ahci: Add generic MSI-X support for single interrupts to SATA PCI driver
authorRobert Richter <rrichter@cavium.com>
Fri, 5 Jun 2015 17:49:25 +0000 (19:49 +0200)
committerTejun Heo <tj@kernel.org>
Tue, 16 Jun 2015 20:12:13 +0000 (16:12 -0400)
This patch adds generic MSI-X support for single interrupts to the
SATA PCI driver. MSI-X support is needed for host controller that only
have MSI-X support implemented, but no MSI or intx. This patch only
adds support for single interrupts, multiple per-port MSI-X interrupts
are not yet implemented.

The new implementation still initializes MSIs first. Only if that
fails, the code tries to enable MSI-X. If that fails too, setup is
continued with intx interrupts.

To not break other chips by this generic code change, there are the
following precautions:

 * Interrupt ranges are not enabled at all.

 * Only single interrupt mode is enabled for msix cap devices. Thus,
   only one interrupt will be setup.

 * During the discussion with Tejun we agreed to change the init
   sequence from msix-msi-intx to msi-msix-intx. Thus, if a device
   offers msi and init does not fail, the msix init code will not be
   executed. This is equivalent to current code.

With this, the code only setups single mode msix as a last resort if
msi fails. No interrupt range is enabled at all. Only one interrupt
will be enabled.

tj: comment edits.

Changes of the patch series:

v5:
 * updated patch subject that the patch only implements single IRQ
 * moved Cavium specific code to a separate patch
 * detect Cavium ThunderX device with PCI_CLASS_STORAGE_SATA_AHCI
   instead of vendor/dev id
 * added more comments to the code
 * enable single msix support for all kind of devices (removing strict
   check)
 * rebased onto update libata/for-4.2 with patch 1, 2 applied

v4:
 * removed implementation of ahci_init_intx()
 * improved patch descriptions
 * rebased onto libata/for-4.2

v3:
 * store irq number in struct ahci_host_priv
 * change initialization order from msix-msi-intx to msi-msix-intx
 * improve comments in ahci_init_msix()
 * improve error message in ahci_init_msix()
 * do not enable MSI-X if MSI is actively disabled for the device

v2:
 * determine irq vector from pci_dev->msi_list

Based on a patch from Sunil Goutham <sgoutham@cavium.com>.

Signed-off-by: Robert Richter <rrichter@cavium.com>
Signed-off-by: Tejun Heo <tj@kernel.org>
drivers/ata/ahci.c

index a3c66c3bb76ebc4c4dc4d59342942d443371bd05..77a34fc0413867dade06e8d17dfb506043074d15 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/device.h>
 #include <linux/dmi.h>
 #include <linux/gfp.h>
+#include <linux/msi.h>
 #include <scsi/scsi_host.h>
 #include <scsi/scsi_cmnd.h>
 #include <linux/libata.h>
@@ -1201,6 +1202,68 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host)
 {}
 #endif
 
+static struct msi_desc *msix_get_desc(struct pci_dev *dev, u16 entry)
+{
+       struct msi_desc *desc;
+
+       list_for_each_entry(desc, &dev->msi_list, list) {
+               if (desc->msi_attrib.entry_nr == entry)
+                       return desc;
+       }
+
+       return NULL;
+}
+
+/*
+ * 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.
+ */
+static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports,
+                         struct ahci_host_priv *hpriv)
+{
+       struct msi_desc *desc;
+       int rc, nvec;
+       struct msix_entry entry = {};
+
+       /* Do not init MSI-X if MSI is disabled for the device */
+       if (hpriv->flags & AHCI_HFLAG_NO_MSI)
+               return -ENODEV;
+
+       nvec = pci_msix_vec_count(pdev);
+       if (nvec < 0)
+               return nvec;
+
+       if (!nvec) {
+               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);
+       if (rc < 0)
+               goto fail;
+
+       desc = msix_get_desc(pdev, 0);  /* first entry */
+       if (!desc) {
+               rc = -EINVAL;
+               goto fail;
+       }
+
+       hpriv->irq = desc->irq;
+
+       return 1;
+fail:
+       dev_err(&pdev->dev,
+               "failed to enable MSI-X with error %d, # of vectors: %d\n",
+               rc, nvec);
+
+       return rc;
+}
+
 static int ahci_init_msi(struct pci_dev *pdev, unsigned int n_ports,
                        struct ahci_host_priv *hpriv)
 {
@@ -1260,6 +1323,15 @@ static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
        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);
+       if (nvec >= 0)
+               return nvec;
+
        /* lagacy intx interrupts */
        pci_intx(pdev, 1);
        hpriv->irq = pdev->irq;