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: 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>
This commit is contained in:
parent
4d92f0099a
commit
d684a90d38
@ -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,
|
|||||||
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,
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
* 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 @@
|
|||||||
#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,
|
|||||||
}
|
}
|
||||||
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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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)
|
|||||||
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);
|
||||||
|
Loading…
Reference in New Issue
Block a user