|
@@ -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)
|