Kristen Carlson Accardi wrote: > Device Initiated Power Management, which is defined > in SATA 2.5 can be enabled for disks which support it. > This patch enables DIPM when the user sets the link > power management policy to "min_power". > > Additionally, libata drivers can define a function > (enable_pm) that will perform hardware specific actions to > enable whatever power management policy the user set up > for Host Initiated Power management (HIPM). > This power management policy will be activated after all > disks have been enumerated and intialized. Drivers should > also define disable_pm, which will turn off link power > management, but not change link power management policy. > > Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@xxxxxxxxx> > --- > Documentation/scsi/link_power_management_policy.txt | 19 + > drivers/ata/libata-core.c | 194 +++++++++++++++++++- > drivers/ata/libata-eh.c | 5 > drivers/ata/libata-scsi.c | 83 ++++++++ > include/linux/ata.h | 7 > include/linux/libata.h | 25 ++ > 6 files changed, 322 insertions(+), 11 deletions(-) > > Index: libata-dev/drivers/ata/libata-scsi.c > =================================================================== > --- libata-dev.orig/drivers/ata/libata-scsi.c 2007-09-24 13:43:10.000000000 -0700 > +++ libata-dev/drivers/ata/libata-scsi.c 2007-09-24 13:46:22.000000000 -0700 > @@ -110,6 +110,78 @@ static struct scsi_transport_template at > }; > > > +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 *)) > { > @@ -3041,6 +3113,17 @@ void ata_scsi_simulate(struct ata_device > } > } > > +int ata_scsi_set_link_pm_policy(struct ata_port *ap, > + enum link_pm policy) > +{ > + ap->pm_policy = policy; > + ap->link.eh_info.action |= ATA_EHI_LPM; > + ap->link.eh_info.flags |= ATA_EHI_NO_AUTOPSY; > + ata_port_schedule_eh(ap); > + return 0; > +} > +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; > Index: libata-dev/include/linux/libata.h > =================================================================== > --- libata-dev.orig/include/linux/libata.h 2007-09-24 13:43:10.000000000 -0700 > +++ libata-dev/include/linux/libata.h 2007-09-24 13:47:57.000000000 -0700 > @@ -140,6 +140,8 @@ enum { > ATA_DFLAG_ACPI_PENDING = (1 << 5), /* ACPI resume action pending */ > ATA_DFLAG_ACPI_FAILED = (1 << 6), /* ACPI on devcfg has failed */ > ATA_DFLAG_AN = (1 << 7), /* device supports AN */ > + ATA_DFLAG_HIPM = (1 << 8), /* device supports HIPM */ > + ATA_DFLAG_DIPM = (1 << 9), /* device supports DIPM */ > ATA_DFLAG_CFG_MASK = (1 << 12) - 1, > > ATA_DFLAG_PIO = (1 << 12), /* device limited to PIO mode */ > @@ -181,6 +183,7 @@ enum { > ATA_FLAG_NO_IORDY = (1 << 16), /* controller lacks iordy */ > ATA_FLAG_ACPI_SATA = (1 << 17), /* need native SATA ACPI layout */ > ATA_FLAG_AN = (1 << 18), /* controller supports AN */ > + ATA_FLAG_IPM = (1 << 19), /* driver can handle IPM */ > > /* The following flag belongs to ap->pflags but is kept in > * ap->flags because it's referenced in many LLDs and will be > @@ -283,6 +286,7 @@ enum { > 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 */ > @@ -308,6 +312,7 @@ enum { > ATA_HORKAGE_NONCQ = (1 << 2), /* Don't use NCQ */ > ATA_HORKAGE_MAX_SEC_128 = (1 << 3), /* Limit max sects to 128 */ > ATA_HORKAGE_BROKEN_HPA = (1 << 4), /* Broken HPA */ > + ATA_HORKAGE_IPM = (1 << 5), /* LPM problems */ > }; > > enum hsm_task_states { > @@ -347,6 +352,18 @@ typedef int (*ata_reset_fn_t)(struct ata > unsigned long deadline); > typedef void (*ata_postreset_fn_t)(struct ata_link *link, unsigned int *classes); > > +/* > + * host pm policy: If you alter this, you also need to alter libata-scsi.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; > @@ -585,6 +602,7 @@ struct ata_port { > > pm_message_t pm_mesg; > int *pm_result; > + enum link_pm pm_policy; > > struct timer_list fastdrain_timer; > unsigned long fastdrain_cnt; > @@ -647,7 +665,8 @@ struct ata_port_operations { > > 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); > > @@ -854,7 +873,9 @@ extern int ata_cable_40wire(struct ata_p > 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); > +extern int ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy); > +extern void ata_dev_disable_pm(struct ata_device *dev); > /* > * Timing helpers > */ > Index: libata-dev/drivers/ata/libata-core.c > =================================================================== > --- libata-dev.orig/drivers/ata/libata-core.c 2007-09-24 13:43:10.000000000 -0700 > +++ libata-dev/drivers/ata/libata-core.c 2007-09-24 13:46:22.000000000 -0700 > @@ -68,7 +68,8 @@ const unsigned long sata_deb_timing_long > static unsigned int ata_dev_init_params(struct ata_device *dev, > u16 heads, u16 sectors); > static unsigned int ata_dev_set_xfermode(struct ata_device *dev); > -static unsigned int ata_dev_set_AN(struct ata_device *dev, u8 enable); > +static unsigned int ata_dev_set_feature(struct ata_device *dev, > + u8 enable, u8 feature); > static void ata_dev_xfermask(struct ata_device *dev); > static unsigned long ata_dev_blacklisted(const struct ata_device *dev); > > @@ -615,6 +616,129 @@ void ata_dev_disable(struct ata_device * > } > } > > +static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) > +{ > + struct ata_link *link = dev->link; > + struct ata_port *ap = link->ap; > + u32 scontrol; > + > + /* > + * disallow DIPM for drivers which haven't set > + * ATA_FLAG_IPM. 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_IPM) || !ata_dev_enabled(dev)) { if (!((ap->flags & ATA_FLAG_IPM) && ata_dev_enabled(dev))) { > + ap->pm_policy = NOT_AVAILABLE; > + 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. > + */ > + sata_scr_read(link, SCR_CONTROL, &scontrol); > + > + switch (policy) { > + case MIN_POWER: > + /* no restrictions on IPM transitions */ > + scontrol &= ~(0x3 << 8); > + sata_scr_write(link, SCR_CONTROL, scontrol); > + > + /* enable DIPM */ > + if (dev->flags & ATA_DFLAG_DIPM) > + ata_dev_set_feature(dev, SETFEATURES_SATA_ENABLE, > + SATA_DIPM); > + break; > + case MEDIUM_POWER: > + /* allow IPM to PARTIAL */ > + scontrol &= ~(0x1 << 8); > + scontrol |= (0x2 << 8); > + sata_scr_write(link, SCR_CONTROL, scontrol); > + > + /* disable DIPM */ > + if (ata_dev_enabled(dev) && (dev->flags & ATA_DFLAG_DIPM)) > + ata_dev_set_feature(dev, SETFEATURES_SATA_DISABLE, > + SATA_DIPM); > + break; > + case NOT_AVAILABLE: > + case MAX_PERFORMANCE: > + /* disable all IPM transitions */ > + scontrol |= (0x3 << 8); > + sata_scr_write(link, SCR_CONTROL, scontrol); > + > + /* disable DIPM */ > + if (ata_dev_enabled(dev) && (dev->flags & ATA_DFLAG_DIPM)) > + ata_dev_set_feature(dev, SETFEATURES_SATA_DISABLE, > + SATA_DIPM); > + break; > + } > + return 0; > +} > + > +/** > + * ata_dev_enable_pm - enable SATA interface power management > + * @device - device to enable ipm for > + * @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 IPM is not supported, 0 otherwise. > + */ > +int ata_dev_enable_pm(struct ata_device *dev, enum link_pm 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->pm_policy = MAX_PERFORMANCE; > + else > + ap->pm_policy = policy; > + return rc; > +} > + > +/** > + * ata_dev_disable_pm - disable SATA interface power management > + * @device - device to enable ipm for > + * > + * 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 > + */ > +void ata_dev_disable_pm(struct ata_device *dev) > +{ > + struct ata_port *ap = dev->link->ap; > + > + ata_dev_set_dipm(dev, MAX_PERFORMANCE); > + if (ap->ops->disable_pm) > + ap->ops->disable_pm(ap); > +} > + > + > /** > * ata_devchk - PATA device presence detection > * @ap: ATA channel to examine > @@ -2029,7 +2153,8 @@ int ata_dev_configure(struct ata_device > if ((ap->flags & ATA_FLAG_AN) && ata_id_has_AN(id)) { > int err; > /* issue SET feature command to turn this on */ > - err = ata_dev_set_AN(dev, SETFEATURES_SATA_ENABLE); > + err = ata_dev_set_feature(dev, SETFEATURES_SATA_ENABLE, > + SATA_AN); > if (err) > ata_dev_printk(dev, KERN_ERR, > "unable to set AN, err %x\n", > @@ -2057,6 +2182,13 @@ int ata_dev_configure(struct ata_device > if (dev->flags & ATA_DFLAG_LBA48) > dev->max_sectors = ATA_MAX_SECTORS_LBA48; > > + if (!(dev->horkage & ATA_HORKAGE_IPM)) { > + if (ata_id_has_hipm(dev->id)) > + dev->flags |= ATA_DFLAG_HIPM; > + if (ata_id_has_dipm(dev->id)) > + dev->flags |= ATA_DFLAG_DIPM; > + } > + > 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 > @@ -2082,6 +2214,13 @@ int ata_dev_configure(struct ata_device > dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128, > dev->max_sectors); > > + if (ata_dev_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); > > @@ -4048,15 +4187,14 @@ static unsigned int ata_dev_set_xfermode > DPRINTK("EXIT, err_mask=%x\n", err_mask); > return err_mask; > } > - > /** > - * ata_dev_set_AN - Issue SET FEATURES - SATA FEATURES > + * ata_dev_set_feature - Issue SET FEATURES - SATA FEATURES > * @dev: Device to which command will be sent > * @enable: Whether to enable or disable the feature > + * @feature: The sector count represents the feature to set > * > * Issue SET FEATURES - SATA FEATURES command to device @dev > - * on port @ap with sector count set to indicate Asynchronous > - * Notification feature > + * on port @ap with sector count > * > * LOCKING: > * PCI/etc. bus probe sem. > @@ -4064,7 +4202,8 @@ static unsigned int ata_dev_set_xfermode > * RETURNS: > * 0 on success, AC_ERR_* mask otherwise. > */ > -static unsigned int ata_dev_set_AN(struct ata_device *dev, u8 enable) > +static unsigned int ata_dev_set_feature(struct ata_device *dev, u8 enable, > + u8 feature) > { > struct ata_taskfile tf; > unsigned int err_mask; > @@ -4077,7 +4216,7 @@ static unsigned int ata_dev_set_AN(struc > tf.feature = enable; > tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE; > tf.protocol = ATA_PROT_NODATA; > - tf.nsect = SATA_AN; > + tf.nsect = feature; > > err_mask = ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0); > > @@ -6031,6 +6170,32 @@ int ata_flush_cache(struct ata_device *d > return 0; > } > > +static void ata_host_disable_link_pm(struct ata_host *host) > +{ > + int i; > + struct ata_link *link; > + struct ata_port *ap; > + struct ata_device *dev; > + > + for (i = 0; i < host->n_ports; i++) { > + ap = host->ports[i]; > + ata_port_for_each_link(link, ap) { > + ata_link_for_each_dev(dev, link) > + ata_dev_disable_pm(dev); > + } > + } > +} > + > +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, > @@ -6101,6 +6266,12 @@ int ata_host_suspend(struct ata_host *ho > { > 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; > @@ -6123,6 +6294,9 @@ void ata_host_resume(struct ata_host *ho > 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 > > @@ -6663,6 +6837,7 @@ int ata_host_register(struct ata_host *h > struct ata_port *ap = host->ports[i]; > > ata_scsi_scan_host(ap, 1); > + ata_scsi_set_link_pm_policy(ap, ap->pm_policy); > } > > return 0; > @@ -7059,7 +7234,8 @@ const struct ata_port_info ata_dummy_por > * likely to change as new drivers are added and updated. > * Do not depend on ABI/API stability. > */ > - > +EXPORT_SYMBOL_GPL(ata_dev_enable_pm); > +EXPORT_SYMBOL_GPL(ata_dev_disable_pm); > EXPORT_SYMBOL_GPL(sata_deb_timing_normal); > EXPORT_SYMBOL_GPL(sata_deb_timing_hotplug); > EXPORT_SYMBOL_GPL(sata_deb_timing_long); > Index: libata-dev/include/linux/ata.h > =================================================================== > --- libata-dev.orig/include/linux/ata.h 2007-09-24 13:43:10.000000000 -0700 > +++ libata-dev/include/linux/ata.h 2007-09-24 13:46:22.000000000 -0700 > @@ -235,6 +235,7 @@ enum { > > /* SETFEATURE Sector counts for SATA features */ > SATA_AN = 0x05, /* Asynchronous Notification */ > + SATA_DIPM = 0x03, /* Device Initiated Power Management */ > > /* ATAPI stuff */ > ATAPI_PKT_DMA = (1 << 0), > @@ -367,6 +368,12 @@ struct ata_taskfile { > ((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)) ) ^ | are you sure this should be 76? we can also change the first statement a bit: (!(((id)[76] == 0x0000) || ((id)[76] == 0xffff)) && \ > +#define ata_id_has_dipm(id) \ > + ( (((id)[76] != 0x0000) && ((id)[76] != 0xffff)) && \ and: (!(((id)[76] == 0x0000) || ((id)[76] == 0xffff)) && \ > + ((id)[78] & (1 << 3)) ) > > static inline int ata_id_has_fua(const u16 *id) > { > Index: libata-dev/Documentation/scsi/link_power_management_policy.txt > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ libata-dev/Documentation/scsi/link_power_management_policy.txt 2007-09-24 13:46:22.000000000 -0700 > @@ -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. > + > + > Index: libata-dev/drivers/ata/libata-eh.c > =================================================================== > --- libata-dev.orig/drivers/ata/libata-eh.c 2007-09-24 13:43:10.000000000 -0700 > +++ libata-dev/drivers/ata/libata-eh.c 2007-09-24 13:46:22.000000000 -0700 > @@ -2400,6 +2400,11 @@ static int ata_eh_recover(struct ata_por > ehc->i.flags &= ~ATA_EHI_SETMODE; > } > > + if (ehc->i.action & ATA_EHI_LPM) { > + ata_link_for_each_dev(dev, link) > + ata_dev_enable_pm(dev, ap->pm_policy); > + } > + > /* this link is okay now */ > ehc->i.flags = 0; > continue; > - To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html