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:
Dan Williams 2015-11-11 16:27:33 -08:00 committed by Tejun Heo
parent 4d92f0099a
commit d684a90d38
3 changed files with 61 additions and 27 deletions

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