Commit d684a90d authored by Dan Williams's avatar Dan Williams Committed by Tejun Heo

ahci: per-port msix support

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: default avatarRicardo Neri <ricardo.neri@intel.com>
[ricardo: fix struct msix_entry handling]
Reported-by: default avatarkernel test robot <ying.huang@linux.intel.com>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
parent 4d92f009
...@@ -1306,15 +1306,13 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host) ...@@ -1306,15 +1306,13 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host)
#endif #endif
/* /*
* ahci_init_msix() only implements single MSI-X support, not multiple * ahci_init_msix() - optionally enable per-port MSI-X otherwise defer
* MSI-X per-port interrupts. This is needed for host controllers that only * to single msi.
* have MSI-X support implemented, but no MSI or intx.
*/ */
static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports, 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; int nvec, i, rc;
struct msix_entry entry = {};
/* Do not init MSI-X if MSI is disabled for the device */ /* Do not init MSI-X if MSI is disabled for the device */
if (hpriv->flags & AHCI_HFLAG_NO_MSI) if (hpriv->flags & AHCI_HFLAG_NO_MSI)
...@@ -1324,22 +1322,39 @@ static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports, ...@@ -1324,22 +1322,39 @@ static int ahci_init_msix(struct pci_dev *pdev, unsigned int n_ports,
if (nvec < 0) if (nvec < 0)
return nvec; 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; rc = -ENODEV;
goto fail; goto fail;
} }
/* for (i = 0; i < nvec; i++)
* There can be more than one vector (e.g. for error detection or hpriv->msix[i].entry = i;
* hdd hotplug). Only the first vector (entry.entry = 0) is used. rc = pci_enable_msix_exact(pdev, hpriv->msix, nvec);
*/
rc = pci_enable_msix_exact(pdev, &entry, 1);
if (rc < 0) if (rc < 0)
goto fail; 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: fail:
dev_err(&pdev->dev, dev_err(&pdev->dev,
"failed to enable MSI-X with error %d, # of vectors: %d\n", "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, ...@@ -1403,20 +1418,25 @@ static int ahci_init_interrupts(struct pci_dev *pdev, unsigned int n_ports,
{ {
int nvec; 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); nvec = ahci_init_msi(pdev, n_ports, hpriv);
if (nvec >= 0) if (nvec >= 0)
return nvec; return nvec;
/* /* try single-msix */
* Currently, MSI-X support only implements single IRQ mode and nvec = ahci_init_msix(pdev, n_ports, hpriv, 0);
* 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) if (nvec >= 0)
return nvec; return nvec;
/* lagacy intx interrupts */ /* legacy intx interrupts */
pci_intx(pdev, 1); pci_intx(pdev, 1);
hpriv->irq = pdev->irq; hpriv->irq = pdev->irq;
...@@ -1578,7 +1598,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) ...@@ -1578,7 +1598,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (!host) if (!host)
return -ENOMEM; return -ENOMEM;
host->private_data = hpriv; 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); ahci_init_interrupts(pdev, n_ports, hpriv);
if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss) if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss)
......
...@@ -242,6 +242,7 @@ enum { ...@@ -242,6 +242,7 @@ enum {
AHCI_HFLAG_NO_FBS = (1 << 18), /* no FBS */ AHCI_HFLAG_NO_FBS = (1 << 18), /* no FBS */
AHCI_HFLAG_EDGE_IRQ = (1 << 19), /* HOST_IRQ_STAT behaves as AHCI_HFLAG_EDGE_IRQ = (1 << 19), /* HOST_IRQ_STAT behaves as
Edge Triggered */ Edge Triggered */
AHCI_HFLAG_MULTI_MSIX = (1 << 20), /* per-port MSI-X */
/* ap->flags bits */ /* ap->flags bits */
...@@ -343,6 +344,7 @@ struct ahci_host_priv { ...@@ -343,6 +344,7 @@ struct ahci_host_priv {
* the PHY position in this array. * the PHY position in this array.
*/ */
struct phy **phys; struct phy **phys;
struct msix_entry *msix; /* Optional MSI-X support */
unsigned nports; /* Number of ports */ unsigned nports; /* Number of ports */
void *plat_data; /* Other platform data */ void *plat_data; /* Other platform data */
unsigned int irq; /* interrupt line */ unsigned int irq; /* interrupt line */
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <scsi/scsi_host.h> #include <scsi/scsi_host.h>
#include <scsi/scsi_cmnd.h> #include <scsi/scsi_cmnd.h>
#include <linux/libata.h> #include <linux/libata.h>
#include <linux/pci.h>
#include "ahci.h" #include "ahci.h"
#include "libata.h" #include "libata.h"
...@@ -2470,9 +2471,10 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv, ...@@ -2470,9 +2471,10 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv,
} }
EXPORT_SYMBOL_GPL(ahci_set_em_messages); 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 scsi_host_template *sht)
{ {
struct ahci_host_priv *hpriv = host->private_data;
int i, rc; int i, rc;
rc = ata_host_start(host); rc = ata_host_start(host);
...@@ -2484,6 +2486,12 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, ...@@ -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++) { for (i = 0; i < host->n_ports; i++) {
struct ahci_port_priv *pp = host->ports[i]->private_data; 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 */ /* Do not receive interrupts sent by dummy ports */
if (!pp) { if (!pp) {
...@@ -2491,14 +2499,15 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, ...@@ -2491,14 +2499,15 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq,
continue; continue;
} }
rc = devm_request_threaded_irq(host->dev, irq + i, rc = devm_request_threaded_irq(host->dev, irq,
ahci_multi_irqs_intr, ahci_multi_irqs_intr,
ahci_port_thread_fn, 0, ahci_port_thread_fn, 0,
pp->irq_desc, host->ports[i]); pp->irq_desc, host->ports[i]);
if (rc) if (rc)
return 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); return ata_host_register(host, sht);
} }
...@@ -2519,8 +2528,8 @@ int ahci_host_activate(struct ata_host *host, struct scsi_host_template *sht) ...@@ -2519,8 +2528,8 @@ int ahci_host_activate(struct ata_host *host, struct scsi_host_template *sht)
int irq = hpriv->irq; int irq = hpriv->irq;
int rc; int rc;
if (hpriv->flags & AHCI_HFLAG_MULTI_MSI) if (hpriv->flags & (AHCI_HFLAG_MULTI_MSI | AHCI_HFLAG_MULTI_MSIX))
rc = ahci_host_activate_multi_irqs(host, irq, sht); rc = ahci_host_activate_multi_irqs(host, sht);
else if (hpriv->flags & AHCI_HFLAG_EDGE_IRQ) else if (hpriv->flags & AHCI_HFLAG_EDGE_IRQ)
rc = ata_host_activate(host, irq, ahci_single_edge_irq_intr, rc = ata_host_activate(host, irq, ahci_single_edge_irq_intr,
IRQF_SHARED, sht); IRQF_SHARED, sht);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment