Re: [patch 1/2] Enable link power management for ata drivers

From: roel
Date: Mon Sep 24 2007 - 19:13:21 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/