@@ -72,6 +72,14 @@ MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ig
static int ahci_enable_alpm(struct ata_port *ap,
enum link_pm policy);
static void ahci_disable_alpm(struct ata_port *ap);
+
+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);
@@ -304,6 +312,8 @@ struct ahci_port_priv {
u32 intr_mask; /* interrupts to enable */
/* enclosure management info per PM slot */
struct ahci_em_priv em_priv[EM_MAX_SLOTS];
+
+ unsigned int alpm_accounting_active:1;
};
static int ahci_scr_read(struct ata_link *link, unsigned int sc_reg, u32 *val);
@@ -359,6 +369,9 @@ DEVICE_ATTR(ahci_host_cap2, S_IRUGO, ahci_show_host_cap2, NULL);
DEVICE_ATTR(ahci_host_version, S_IRUGO, ahci_show_host_version, NULL);
DEVICE_ATTR(ahci_port_cmd, S_IRUGO, ahci_show_port_cmd, NULL);
+DEVICE_ATTR(ahci_alpm_accounting, S_IRUGO | S_IWUSR,
+ ahci_alpm_show_accounting, ahci_alpm_set_accounting);
+
static struct device_attribute *ahci_shost_attrs[] = {
&dev_attr_link_power_management_policy,
&dev_attr_em_message_type,
@@ -367,6 +380,10 @@ static struct device_attribute *ahci_shost_attrs[] = {
&dev_attr_ahci_host_cap2,
&dev_attr_ahci_host_version,
&dev_attr_ahci_port_cmd,
+ &dev_attr_ata_alpm_active,
+ &dev_attr_ata_alpm_partial,
+ &dev_attr_ata_alpm_slumber,
+ &dev_attr_ahci_alpm_accounting,
NULL
};
@@ -1165,9 +1182,14 @@ static int ahci_enable_alpm(struct ata_port *ap,
* getting woken up due to spurious phy ready interrupts
* TBD - Hot plug should be done via polling now, is
* that even supported?
+ *
+ * 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->alpm_accounting_active) {
+ 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
@@ -2157,6 +2179,41 @@ static void ahci_error_intr(struct ata_port *ap, u32 irq_stat)
ata_port_abort(ap);
}
+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 = ap->private_data;
+
+ return sprintf(buf, "%u\n", pp->alpm_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 = ap->private_data;
+ void __iomem *port_mmio = ahci_port_base(ap);
+
+ if (buf[0] == '0')
+ pp->alpm_accounting_active = 0;
+ if (buf[0] == '1')
+ pp->alpm_accounting_active = 1;
+
+ /* we need to enable the PHYRDY interrupt when we want accounting */
+ if (pp->alpm_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);
@@ -2182,6 +2239,7 @@ static void ahci_port_intr(struct ata_port *ap)
if ((hpriv->flags & AHCI_HFLAG_NO_HOTPLUG) &&
(status & PORT_IRQ_PHYRDY)) {
status &= ~PORT_IRQ_PHYRDY;
+ ata_account_alpm_stats(ap);
ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18)));
}
@@ -6733,6 +6733,102 @@ const struct ata_port_info ata_dummy_port_info = {
.port_ops = &ata_dummy_port_ops,
};
+/* ALPM state accounting helpers */
+static int get_current_alpm_state(struct ata_port *ap)
+{
+ u32 status = 0;
+
+ sata_scr_read(&ap->link, SCR_STATUS, &status);
+
+ /* link status is in bits 11-8 */
+ status = status >> 8;
+ status = status & 0x7;
+
+ if (status == 6)
+ return ALPM_PORT_SLUMBER;
+ if (status == 2)
+ return ALPM_PORT_PARTIAL;
+ if (status == 1)
+ return ALPM_PORT_ACTIVE;
+ return ALPM_PORT_NOLINK;
+}
+
+void ata_account_alpm_stats(struct ata_port *ap)
+{
+ int new_state;
+ u64 new_jiffies, jiffies_delta;
+
+ new_state = get_current_alpm_state(ap);
+ new_jiffies = jiffies;
+
+ jiffies_delta = new_jiffies - ap->link.ata_link_stats.previous_jiffies;
+
+ switch (ap->link.ata_link_stats.previous_state) {
+ case ALPM_PORT_NOLINK:
+ ap->link.ata_link_stats.active_jiffies = 0;
+ ap->link.ata_link_stats.partial_jiffies = 0;
+ ap->link.ata_link_stats.slumber_jiffies = 0;
+ break;
+ case ALPM_PORT_ACTIVE:
+ ap->link.ata_link_stats.active_jiffies += jiffies_delta;
+ break;
+ case ALPM_PORT_PARTIAL:
+ ap->link.ata_link_stats.partial_jiffies += jiffies_delta;
+ break;
+ case ALPM_PORT_SLUMBER:
+ ap->link.ata_link_stats.slumber_jiffies += jiffies_delta;
+ break;
+ default:
+ break;
+ }
+ ap->link.ata_link_stats.previous_state = new_state;
+ ap->link.ata_link_stats.previous_jiffies = new_jiffies;
+}
+
+ssize_t ata_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);
+
+ ata_account_alpm_stats(ap);
+
+ return sprintf(buf, "%u\n",
+ jiffies_to_msecs(ap->link.ata_link_stats.active_jiffies));
+}
+
+ssize_t ata_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);
+
+ ata_account_alpm_stats(ap);
+
+ return sprintf(buf, "%u\n",
+ jiffies_to_msecs(ap->link.ata_link_stats.partial_jiffies));
+}
+
+ssize_t ata_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);
+
+ ata_account_alpm_stats(ap);
+
+ return sprintf(buf, "%u\n",
+ jiffies_to_msecs(ap->link.ata_link_stats.slumber_jiffies));
+}
+
+DEVICE_ATTR(ata_alpm_active, S_IRUGO, ata_alpm_show_active, NULL);
+DEVICE_ATTR(ata_alpm_partial, S_IRUGO, ata_alpm_show_partial, NULL);
+DEVICE_ATTR(ata_alpm_slumber, S_IRUGO, ata_alpm_show_slumber, NULL);
+EXPORT_SYMBOL_GPL(dev_attr_ata_alpm_active);
+EXPORT_SYMBOL_GPL(dev_attr_ata_alpm_partial);
+EXPORT_SYMBOL_GPL(dev_attr_ata_alpm_slumber);
+
+
/*
* libata is essentially a library of internal helper functions for
* low-level ATA host controller drivers. As such, the API/ABI is
@@ -682,6 +682,22 @@ struct ata_acpi_gtm {
u32 flags;
} __packed;
+/* ALPM accounting state and stats */
+enum alpm_port_states {
+ ALPM_PORT_NOLINK = 0,
+ ALPM_PORT_ACTIVE = 1,
+ ALPM_PORT_PARTIAL = 2,
+ ALPM_PORT_SLUMBER = 3
+};
+
+struct ata_link_stats {
+ u64 active_jiffies;
+ u64 partial_jiffies;
+ u64 slumber_jiffies;
+ int previous_state;
+ int previous_jiffies;
+};
+
struct ata_link {
struct ata_port *ap;
int pmp; /* port multiplier port # */
@@ -702,6 +718,8 @@ struct ata_link {
struct ata_eh_context eh_context;
struct ata_device device[ATA_MAX_DEVICES];
+
+ struct ata_link_stats ata_link_stats;
};
struct ata_port {
@@ -1046,6 +1064,18 @@ extern void ata_timing_merge(const struct ata_timing *,
unsigned int);
extern u8 ata_timing_cycle2mode(unsigned int xfer_shift, int cycle);
+/* alpm accounting helpers */
+extern ssize_t ata_alpm_show_active(struct device *dev,
+ struct device_attribute *attr, char *buf);
+extern ssize_t ata_alpm_show_slumber(struct device *dev,
+ struct device_attribute *attr, char *buf);
+extern ssize_t ata_alpm_show_partial(struct device *dev,
+ struct device_attribute *attr, char *buf);
+extern void ata_account_alpm_stats(struct ata_port *ap);
+extern struct device_attribute dev_attr_ata_alpm_active;
+extern struct device_attribute dev_attr_ata_alpm_partial;
+extern struct device_attribute dev_attr_ata_alpm_slumber;
+
/* PCI */
#ifdef CONFIG_PCI
struct pci_dev;