ahci: per-port msix support
authorDan Williams <dan.j.williamps@intel.com>
Thu, 12 Nov 2015 00:27:33 +0000 (16:27 -0800)
committerTejun Heo <tj@kernel.org>
Mon, 16 Nov 2015 16:29:10 +0000 (11:29 -0500)
Some AHCI controllers support per-port MSI-X vectors.  At the same time
the Linux AHCI driver needs to support one-off architectures that
implement a single MSI-X vector for all ports.  The heuristic for
enabling AHCI ports becomes, in order of preference:

1/ per-port multi-MSI-X

2/ per-port multi-MSI

3/ single MSI

4/ single MSI-X

5/ legacy INTX

This all depends on AHCI implementations with potentially broken MSI-X
requesting less vectors than the number of ports.  If this assumption is
violated we will need to start explicitly white-listing AHCI-MSIX
implementations.

Reported-by: Ricardo Neri <ricardo.neri@intel.com>
[ricardo: fix struct msix_entry handling]
Reported-by: kernel test robot <ying.huang@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
drivers/ata/ahci.c
drivers/ata/ahci.h
drivers/ata/libahci.c

index cdfbcc54821fd6ea3a5dd6a11c969f12055c7e24..594fcabd22cd16bfcc09626338a3da33481497cc 100644 (file)
@@ -1306,15 +1306,13 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host)
 #endif
 
 /*
- * ahci_init_msix() only implements single MSI-X support, not multiple
- * MSI-X per-port interrupts. This is needed for host controllers that only
- * have MSI-X support implemented, but no MSI or intx.
+ * ahci_init_msix() - optionally enable per-port MSI-X otherwise defer
+ * to single msi.
  */
 static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports,
-                         struct ahci_host_priv *hpriv)
+                         struct ahci_host_priv *hpriv, unsigned long flags)
 {
-       int rc, nvec;
-       struct msix_entry entry = {};
+       int nvec, i, rc;
 
        /* Do not init MSI-X if MSI is disabled for the device */
        if (hpriv->flags & AHCI_HFLAG_NO_MSI)
@@ -1324,22 +1322,39 @@ static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports,
        if (nvec < 0)
                return nvec;
 
-       if (!nvec) {
+       /*
+        * Proper MSI-X implementations will have a vector per-port.
+        * Barring that, we prefer single-MSI over single-MSIX.  If this
+        * check fails (not enough MSI-X vectors for all ports) we will
+        * be called again with the flag clear iff ahci_init_msi()
+        * fails.
+        */
+       if (flags & AHCI_HFLAG_MULTI_MSIX) {
+               if (nvec < n_ports)
+                       return -ENODEV;
+               nvec = n_ports;
+       } else if (nvec) {
+               nvec = 1;
+       } else {
+               /*
+                * Emit dev_err() since this was the non-legacy irq
+                * method of last resort.
+                */
                rc = -ENODEV;
                goto fail;
        }
 
-       /*
-        * There can be more than one vector (e.g. for error detection or
-        * hdd hotplug). Only the first vector (entry.entry = 0) is used.
-        */
-       rc = pci_enable_msix_exact(pdev, &entry, 1);
+       for (i = 0; i < nvec; i++)
+               hpriv->msix[i].entry = i;
+       rc = pci_enable_msix_exact(pdev, hpriv->msix, nvec);
        if (rc < 0)
                goto fail;
 
-       hpriv->irq = entry.vector;
+       if (nvec > 1)
+               hpriv->flags |= AHCI_HFLAG_MULTI_MSIX;
+       hpriv->irq = hpriv->msix[0].vector; /* for single msi-x */
 
-       return 1;
+       return nvec;
 fail:
        dev_err(&pdev->dev,
                "failed to enable MSI-X with error %d, # of vectors: %d\n",
@@ -1403,20 +1418,25 @@ static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
 {
        int nvec;
 
+       /*
+        * Try to enable per-port MSI-X.  If the host is not capable
+        * fall back to single MSI before finally attempting single
+        * MSI-X.
+        */
+       nvec = ahci_init_msix(pdev, n_ports, hpriv, AHCI_HFLAG_MULTI_MSIX);
+       if (nvec >= 0)
+               return nvec;
+
        nvec = ahci_init_msi(pdev, n_ports, hpriv);
        if (nvec >= 0)
                return nvec;
 
-       /*
-        * Currently, MSI-X support only implements single IRQ mode and
-        * exists for controllers which can't do other types of IRQ. Only
-        * set it up if MSI fails.
-        */
-       nvec = ahci_init_msix(pdev, n_ports, hpriv);
+       /* try single-msix */
+       nvec = ahci_init_msix(pdev, n_ports, hpriv, 0);
        if (nvec >= 0)
                return nvec;
 
-       /* lagacy intx interrupts */
+       /* legacy intx interrupts */
        pci_intx(pdev, 1);
        hpriv->irq = pdev->irq;
 
@@ -1578,7 +1598,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        if (!host)
                return -ENOMEM;
        host->private_data = hpriv;
-
+       hpriv->msix = devm_kzalloc(&pdev->dev,
+                       sizeof(struct msix_entry) * n_ports, GFP_KERNEL);
+       if (!hpriv->msix)
+               return -ENOMEM;
        ahci_init_interrupts(pdev, n_ports, hpriv);
 
        if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
index 45586c1dbbdc1e225947c09e200bf5b2c2296a15..9e60c50b2103d11d1ff5e4f98cdd24a39ebd6760 100644 (file)
@@ -242,6 +242,7 @@ enum {
        AHCI_HFLAG_NO_FBS               = (1 << 18), /* no FBS */
        AHCI_HFLAG_EDGE_IRQ             = (1 << 19), /* HOST_IRQ_STAT behaves as
                                                        Edge Triggered */
+       AHCI_HFLAG_MULTI_MSIX           = (1 << 20), /* per-port MSI-X */
 
        /* ap->flags bits */
 
@@ -343,6 +344,7 @@ struct ahci_host_priv {
         * the PHY position in this array.
         */
        struct phy              **phys;
+       struct msix_entry       *msix;          /* Optional MSI-X support */
        unsigned                nports;         /* Number of ports */
        void                    *plat_data;     /* Other platform data */
        unsigned int            irq;            /* interrupt line */
index 096064cd6c52b1b0f72bf710d1a56893bd6bff9c..0a5645fb02f8153896964ae15473583c46c3c72e 100644 (file)
@@ -43,6 +43,7 @@
 #include <scsi/scsi_host.h>
 #include <scsi/scsi_cmnd.h>
 #include <linux/libata.h>
+#include <linux/pci.h>
 #include "ahci.h"
 #include "libata.h"
 
@@ -2470,9 +2471,10 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv,
 }
 EXPORT_SYMBOL_GPL(ahci_set_em_messages);
 
-static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq,
+static int ahci_host_activate_multi_irqs(struct ata_host *host,
                                         struct scsi_host_template *sht)
 {
+       struct ahci_host_priv *hpriv = host->private_data;
        int i, rc;
 
        rc = ata_host_start(host);
@@ -2484,6 +2486,12 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq,
         */
        for (i = 0; i < host->n_ports; i++) {
                struct ahci_port_priv *pp = host->ports[i]->private_data;
+               int irq;
+
+               if (hpriv->flags & AHCI_HFLAG_MULTI_MSIX)
+                       irq = hpriv->msix[i].vector;
+               else
+                       irq = hpriv->irq + i;
 
                /* Do not receive interrupts sent by dummy ports */
                if (!pp) {
@@ -2491,14 +2499,15 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq,
                        continue;
                }
 
-               rc = devm_request_threaded_irq(host->dev, irq + i,
+               rc = devm_request_threaded_irq(host->dev, irq,
                                               ahci_multi_irqs_intr,
                                               ahci_port_thread_fn, 0,
                                               pp->irq_desc, host->ports[i]);
                if (rc)
                        return rc;
-               ata_port_desc(host->ports[i], "irq %d", irq + i);
+               ata_port_desc(host->ports[i], "irq %d", irq);
        }
+
        return ata_host_register(host, sht);
 }
 
@@ -2519,8 +2528,8 @@ int ahci_host_activate(struct ata_host *host, struct scsi_host_template *sht)
        int irq = hpriv->irq;
        int rc;
 
-       if (hpriv->flags & AHCI_HFLAG_MULTI_MSI)
-               rc = ahci_host_activate_multi_irqs(host, irq, sht);
+       if (hpriv->flags & (AHCI_HFLAG_MULTI_MSI | AHCI_HFLAG_MULTI_MSIX))
+               rc = ahci_host_activate_multi_irqs(host, sht);
        else if (hpriv->flags & AHCI_HFLAG_EDGE_IRQ)
                rc = ata_host_activate(host, irq, ahci_single_edge_irq_intr,
                                       IRQF_SHARED, sht);