| From 3d1f00627a8db9d71091db32cd8109962f69ec43 Mon Sep 17 00:00:00 2001 |
| From: Arjan van de Ven <arjan at linux.intel.com> |
| Date: Sat, 31 Mar 2012 20:35:12 +0200 |
| Subject: [PATCH 2/2] libata: Add ALPM power state accounting to the AHCI |
| driver |
| |
| PowerTOP wants to be able to show the user how effective the ALPM link |
| power management is for the user. ALPM is worth around 0.5W on a quiet |
| link; PowerTOP wants to be able to find cases where the "quiet link" isn't |
| actually quiet. |
| |
| This patch adds state accounting functionality to the AHCI driver for |
| PowerTOP to use. |
| The parts of the patch are |
| 1) the sysfs logic of exposing the stats for each state in sysfs |
| 2) the basic accounting logic that gets update on link change interrupts |
| (or when the user accesses the info from sysfs) |
| 3) a "accounting enable" flag; in order to get the accounting to work, |
| the driver needs to get phyrdy interrupts on link status changes. |
| Normally and currently this is disabled by the driver when ALPM is |
| on (to reduce overhead); when PowerTOP is running this will need |
| to be on to get usable statistics... hence the sysfs tunable. |
| |
| The PowerTOP output currently looks like this: |
| |
| Recent SATA AHCI link activity statistics |
| Active Partial Slumber Device name |
| 0.5% 99.5% 0.0% host0 |
| |
| (work to resolve "host0" to a more human readable name is in progress) |
| |
| Signed-off-by: Arjan van de Ven <arjan at linux.intel.com> |
| --- |
| drivers/ata/ahci.h | 15 ++++ |
| drivers/ata/libahci.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++- |
| 2 files changed, 200 insertions(+), 2 deletions(-) |
| |
| diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h |
| index b175000..38297f9 100644 |
| --- a/drivers/ata/ahci.h |
| +++ b/drivers/ata/ahci.h |
| @@ -264,6 +264,13 @@ struct ahci_em_priv { |
| unsigned long led_state; |
| }; |
| |
| +enum ahci_port_states { |
| + AHCI_PORT_NOLINK = 0, |
| + AHCI_PORT_ACTIVE = 1, |
| + AHCI_PORT_PARTIAL = 2, |
| + AHCI_PORT_SLUMBER = 3 |
| +}; |
| + |
| struct ahci_port_priv { |
| struct ata_link *active_link; |
| struct ahci_cmd_hdr *cmd_slot; |
| @@ -282,6 +289,14 @@ struct ahci_port_priv { |
| int fbs_last_dev; /* save FBS.DEV of last FIS */ |
| /* enclosure management info per PM slot */ |
| struct ahci_em_priv em_priv[EM_MAX_SLOTS]; |
| + |
| + /* ALPM accounting state and stats */ |
| + unsigned int accounting_active:1; |
| + u64 active_jiffies; |
| + u64 partial_jiffies; |
| + u64 slumber_jiffies; |
| + int previous_state; |
| + int previous_jiffies; |
| }; |
| |
| struct ahci_host_priv { |
| diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c |
| index a72bfd0..5859215 100644 |
| --- a/drivers/ata/libahci.c |
| +++ b/drivers/ata/libahci.c |
| @@ -58,6 +58,17 @@ MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ig |
| |
| static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, |
| unsigned hints); |
| +static ssize_t ahci_alpm_show_active(struct device *dev, |
| + struct device_attribute *attr, char *buf); |
| +static ssize_t ahci_alpm_show_slumber(struct device *dev, |
| + struct device_attribute *attr, char *buf); |
| +static ssize_t ahci_alpm_show_partial(struct device *dev, |
| + struct device_attribute *attr, char *buf); |
| +static ssize_t ahci_alpm_show_accounting(struct device *dev, |
| + struct device_attribute *attr, char *buf); |
| +static ssize_t ahci_alpm_set_accounting(struct device *dev, |
| + struct device_attribute *attr, |
| + const char *buf, size_t count); |
| 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); |
| @@ -118,6 +129,12 @@ static DEVICE_ATTR(ahci_host_caps, S_IRUGO, ahci_show_host_caps, NULL); |
| static DEVICE_ATTR(ahci_host_cap2, S_IRUGO, ahci_show_host_cap2, NULL); |
| static DEVICE_ATTR(ahci_host_version, S_IRUGO, ahci_show_host_version, NULL); |
| static DEVICE_ATTR(ahci_port_cmd, S_IRUGO, ahci_show_port_cmd, NULL); |
| +static DEVICE_ATTR(ahci_alpm_active, S_IRUGO, ahci_alpm_show_active, NULL); |
| +static DEVICE_ATTR(ahci_alpm_partial, S_IRUGO, ahci_alpm_show_partial, NULL); |
| +static DEVICE_ATTR(ahci_alpm_slumber, S_IRUGO, ahci_alpm_show_slumber, NULL); |
| +static DEVICE_ATTR(ahci_alpm_accounting, S_IRUGO | S_IWUSR, |
| + ahci_alpm_show_accounting, ahci_alpm_set_accounting); |
| + |
| static DEVICE_ATTR(em_buffer, S_IWUSR | S_IRUGO, |
| ahci_read_em_buffer, ahci_store_em_buffer); |
| static DEVICE_ATTR(em_message_supported, S_IRUGO, ahci_show_em_supported, NULL); |
| @@ -130,6 +147,10 @@ struct device_attribute *ahci_shost_attrs[] = { |
| &dev_attr_ahci_host_cap2, |
| &dev_attr_ahci_host_version, |
| &dev_attr_ahci_port_cmd, |
| + &dev_attr_ahci_alpm_active, |
| + &dev_attr_ahci_alpm_partial, |
| + &dev_attr_ahci_alpm_slumber, |
| + &dev_attr_ahci_alpm_accounting, |
| &dev_attr_em_buffer, |
| &dev_attr_em_message_supported, |
| NULL |
| @@ -673,9 +694,14 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, |
| * Disable interrupts on Phy Ready. This keeps us from |
| * getting woken up due to spurious phy ready |
| * interrupts. |
| + * |
| + * However, when accounting_active is set, we do want |
| + * the interrupts for accounting purposes. |
| */ |
| - pp->intr_mask &= ~PORT_IRQ_PHYRDY; |
| - writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); |
| + if (!pp->accounting_active) { |
| + pp->intr_mask &= ~PORT_IRQ_PHYRDY; |
| + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); |
| + } |
| |
| sata_link_scr_lpm(link, policy, false); |
| } |
| @@ -1633,6 +1659,162 @@ static void ahci_error_intr(struct ata_port *ap, u32 irq_stat) |
| ata_port_abort(ap); |
| } |
| |
| +static int get_current_alpm_state(struct ata_port *ap) |
| +{ |
| + u32 status = 0; |
| + |
| + ahci_scr_read(&ap->link, SCR_STATUS, &status); |
| + |
| + /* link status is in bits 11-8 */ |
| + status = status >> 8; |
| + status = status & 0x7; |
| + |
| + if (status == 6) |
| + return AHCI_PORT_SLUMBER; |
| + if (status == 2) |
| + return AHCI_PORT_PARTIAL; |
| + if (status == 1) |
| + return AHCI_PORT_ACTIVE; |
| + return AHCI_PORT_NOLINK; |
| +} |
| + |
| +static void account_alpm_stats(struct ata_port *ap) |
| +{ |
| + struct ahci_port_priv *pp; |
| + |
| + int new_state; |
| + u64 new_jiffies, jiffies_delta; |
| + |
| + if (ap == NULL) |
| + return; |
| + pp = ap->private_data; |
| + |
| + if (!pp) return; |
| + |
| + new_state = get_current_alpm_state(ap); |
| + new_jiffies = jiffies; |
| + |
| + jiffies_delta = new_jiffies - pp->previous_jiffies; |
| + |
| + switch (pp->previous_state) { |
| + case AHCI_PORT_NOLINK: |
| + pp->active_jiffies = 0; |
| + pp->partial_jiffies = 0; |
| + pp->slumber_jiffies = 0; |
| + break; |
| + case AHCI_PORT_ACTIVE: |
| + pp->active_jiffies += jiffies_delta; |
| + break; |
| + case AHCI_PORT_PARTIAL: |
| + pp->partial_jiffies += jiffies_delta; |
| + break; |
| + case AHCI_PORT_SLUMBER: |
| + pp->slumber_jiffies += jiffies_delta; |
| + break; |
| + default: |
| + break; |
| + } |
| + pp->previous_state = new_state; |
| + pp->previous_jiffies = new_jiffies; |
| +} |
| + |
| +static ssize_t ahci_alpm_show_active(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct Scsi_Host *shost = class_to_shost(dev); |
| + struct ata_port *ap = ata_shost_to_port(shost); |
| + struct ahci_port_priv *pp; |
| + |
| + if (!ap || ata_port_is_dummy(ap)) |
| + return -EINVAL; |
| + pp = ap->private_data; |
| + account_alpm_stats(ap); |
| + |
| + return sprintf(buf, "%u\n", jiffies_to_msecs(pp->active_jiffies)); |
| +} |
| + |
| +static ssize_t ahci_alpm_show_partial(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct Scsi_Host *shost = class_to_shost(dev); |
| + struct ata_port *ap = ata_shost_to_port(shost); |
| + struct ahci_port_priv *pp; |
| + |
| + if (!ap || ata_port_is_dummy(ap)) |
| + return -EINVAL; |
| + |
| + pp = ap->private_data; |
| + account_alpm_stats(ap); |
| + |
| + return sprintf(buf, "%u\n", jiffies_to_msecs(pp->partial_jiffies)); |
| +} |
| + |
| +static ssize_t ahci_alpm_show_slumber(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct Scsi_Host *shost = class_to_shost(dev); |
| + struct ata_port *ap = ata_shost_to_port(shost); |
| + struct ahci_port_priv *pp; |
| + |
| + if (!ap || ata_port_is_dummy(ap)) |
| + return -EINVAL; |
| + |
| + pp = ap->private_data; |
| + |
| + account_alpm_stats(ap); |
| + |
| + return sprintf(buf, "%u\n", jiffies_to_msecs(pp->slumber_jiffies)); |
| +} |
| + |
| +static ssize_t ahci_alpm_show_accounting(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct Scsi_Host *shost = class_to_shost(dev); |
| + struct ata_port *ap = ata_shost_to_port(shost); |
| + struct ahci_port_priv *pp; |
| + |
| + if (!ap || ata_port_is_dummy(ap)) |
| + return -EINVAL; |
| + |
| + pp = ap->private_data; |
| + |
| + return sprintf(buf, "%u\n", pp->accounting_active); |
| +} |
| + |
| +static ssize_t ahci_alpm_set_accounting(struct device *dev, |
| + struct device_attribute *attr, |
| + const char *buf, size_t count) |
| +{ |
| + unsigned long flags; |
| + struct Scsi_Host *shost = class_to_shost(dev); |
| + struct ata_port *ap = ata_shost_to_port(shost); |
| + struct ahci_port_priv *pp; |
| + void __iomem *port_mmio; |
| + |
| + if (!ap || ata_port_is_dummy(ap)) |
| + return 1; |
| + |
| + pp = ap->private_data; |
| + port_mmio = ahci_port_base(ap); |
| + |
| + if (!pp) |
| + return 1; |
| + if (buf[0] == '0') |
| + pp->accounting_active = 0; |
| + if (buf[0] == '1') |
| + pp->accounting_active = 1; |
| + |
| + /* we need to enable the PHYRDY interrupt when we want accounting */ |
| + if (pp->accounting_active) { |
| + spin_lock_irqsave(ap->lock, flags); |
| + pp->intr_mask |= PORT_IRQ_PHYRDY; |
| + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); |
| + spin_unlock_irqrestore(ap->lock, flags); |
| + } |
| + return count; |
| +} |
| + |
| + |
| static void ahci_port_intr(struct ata_port *ap) |
| { |
| void __iomem *port_mmio = ahci_port_base(ap); |
| @@ -1653,6 +1835,7 @@ static void ahci_port_intr(struct ata_port *ap) |
| /* if LPM is enabled, PHYRDY doesn't mean anything */ |
| if (ap->link.lpm_policy > ATA_LPM_MAX_POWER) { |
| status &= ~PORT_IRQ_PHYRDY; |
| + account_alpm_stats(ap); |
| ahci_scr_write(&ap->link, SCR_ERROR, SERR_PHYRDY_CHG); |
| } |
| |
| -- |
| 1.7.8.5 |
| |