diff options
Diffstat (limited to '')
-rw-r--r-- | patches/linux-3.9.0-ahci-alpm-accounting.patch | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/patches/linux-3.9.0-ahci-alpm-accounting.patch b/patches/linux-3.9.0-ahci-alpm-accounting.patch new file mode 100644 index 0000000..e7d8df6 --- /dev/null +++ b/patches/linux-3.9.0-ahci-alpm-accounting.patch @@ -0,0 +1,306 @@ +From 9abcb86b6dffd17a90e841e8ae3ddd1a841f2feb 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] 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 b830e6c..b7075da 100644 +--- a/drivers/ata/ahci.h ++++ b/drivers/ata/ahci.h +@@ -286,6 +286,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; +@@ -306,6 +313,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 34c8216..cf0d61d 100644 +--- a/drivers/ata/libahci.c ++++ b/drivers/ata/libahci.c +@@ -59,6 +59,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); +@@ -120,6 +131,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); +@@ -132,6 +149,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 +@@ -679,9 +700,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); + } +@@ -1655,6 +1681,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_handle_port_interrupt(struct ata_port *ap, + void __iomem *port_mmio, u32 status) + { +@@ -1672,6 +1854,7 @@ static void ahci_handle_port_interrupt(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.8.0 + |