diff mbox

[v3,-tip,5/5] AHCI: Support multiple MSIs

Message ID cc6441a31a2c600ad9ae33e7715de65b8d46a66f.1349074231.git.agordeev@redhat.com
State Not Applicable
Delegated to: David Miller
Headers show

Commit Message

Alexander Gordeev Oct. 1, 2012, 8:13 a.m. UTC
Take advantage of multiple MSIs implementation on x86 - on systems with
IRQ remapping AHCI ports not only get assigned separate MSI vectors -
but also separate IRQs. As result, interrupts generated by different
ports could be serviced on different CPUs rather than on a single one.

In cases when number of allocated MSIs is less than requested the Sharing
Last MSI mode does not get used, no matter implemented in hardware or not.
Instead, the driver assumes the advantage of multiple MSIs is negated and
falls back to the single MSI mode as if MRSM bit was set (some Intel chips
implement this strategy anyway - MRSM bit gets set even if the number of
allocated MSIs exceeds the number of implemented ports).

Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
---
 drivers/ata/ahci.c    |   91 ++++++++++++++++++++++++++++++++++++--
 drivers/ata/ahci.h    |    6 +++
 drivers/ata/libahci.c |  118 ++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 205 insertions(+), 10 deletions(-)

Comments

Jeff Garzik Oct. 2, 2012, 3:04 a.m. UTC | #1
On 10/01/2012 04:13 AM, Alexander Gordeev wrote:
> Take advantage of multiple MSIs implementation on x86 - on systems with
> IRQ remapping AHCI ports not only get assigned separate MSI vectors -
> but also separate IRQs. As result, interrupts generated by different
> ports could be serviced on different CPUs rather than on a single one.
>
> In cases when number of allocated MSIs is less than requested the Sharing
> Last MSI mode does not get used, no matter implemented in hardware or not.
> Instead, the driver assumes the advantage of multiple MSIs is negated and
> falls back to the single MSI mode as if MRSM bit was set (some Intel chips
> implement this strategy anyway - MRSM bit gets set even if the number of
> allocated MSIs exceeds the number of implemented ports).
>
> Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
> ---
>   drivers/ata/ahci.c    |   91 ++++++++++++++++++++++++++++++++++++--
>   drivers/ata/ahci.h    |    6 +++
>   drivers/ata/libahci.c |  118 ++++++++++++++++++++++++++++++++++++++++++++++---
>   3 files changed, 205 insertions(+), 10 deletions(-)

Acked-by: Jeff Garzik <jgarzik@redhat.com>

Normally, this amount of changes would -really- need to go through the 
libata tree.  However, given the amount of dependencies, it either needs 
a merge tree or to go through the PCI tree...?

Any maintainer comments on disposition?

	Jeff




--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjorn Helgaas Oct. 2, 2012, 4:21 a.m. UTC | #2
On Mon, Oct 1, 2012 at 9:04 PM, Jeff Garzik <jgarzik@pobox.com> wrote:
> On 10/01/2012 04:13 AM, Alexander Gordeev wrote:
>>
>> Take advantage of multiple MSIs implementation on x86 - on systems with
>> IRQ remapping AHCI ports not only get assigned separate MSI vectors -
>> but also separate IRQs. As result, interrupts generated by different
>> ports could be serviced on different CPUs rather than on a single one.
>>
>> In cases when number of allocated MSIs is less than requested the Sharing
>> Last MSI mode does not get used, no matter implemented in hardware or not.
>> Instead, the driver assumes the advantage of multiple MSIs is negated and
>> falls back to the single MSI mode as if MRSM bit was set (some Intel chips
>> implement this strategy anyway - MRSM bit gets set even if the number of
>> allocated MSIs exceeds the number of implemented ports).
>>
>> Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
>> ---
>>   drivers/ata/ahci.c    |   91 ++++++++++++++++++++++++++++++++++++--
>>   drivers/ata/ahci.h    |    6 +++
>>   drivers/ata/libahci.c |  118
>> ++++++++++++++++++++++++++++++++++++++++++++++---
>>   3 files changed, 205 insertions(+), 10 deletions(-)
>
>
> Acked-by: Jeff Garzik <jgarzik@redhat.com>
>
> Normally, this amount of changes would -really- need to go through the
> libata tree.  However, given the amount of dependencies, it either needs a
> merge tree or to go through the PCI tree...?
>
> Any maintainer comments on disposition?

For what it's worth, the bulk of this change is outside PCI, so it
doesn't seem to me like it should go through the PCI tree.  I think I
did ack the part that touched PCI, and there's not much activity in
the PCI MSI area right now, so I'm fine with it going through libata
or whatever people think makes sense.
--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jeff Garzik Oct. 2, 2012, 4:48 a.m. UTC | #3
On 10/02/2012 12:21 AM, Bjorn Helgaas wrote:
> On Mon, Oct 1, 2012 at 9:04 PM, Jeff Garzik <jgarzik@pobox.com> wrote:
>> On 10/01/2012 04:13 AM, Alexander Gordeev wrote:
>>>
>>> Take advantage of multiple MSIs implementation on x86 - on systems with
>>> IRQ remapping AHCI ports not only get assigned separate MSI vectors -
>>> but also separate IRQs. As result, interrupts generated by different
>>> ports could be serviced on different CPUs rather than on a single one.
>>>
>>> In cases when number of allocated MSIs is less than requested the Sharing
>>> Last MSI mode does not get used, no matter implemented in hardware or not.
>>> Instead, the driver assumes the advantage of multiple MSIs is negated and
>>> falls back to the single MSI mode as if MRSM bit was set (some Intel chips
>>> implement this strategy anyway - MRSM bit gets set even if the number of
>>> allocated MSIs exceeds the number of implemented ports).
>>>
>>> Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
>>> ---
>>>    drivers/ata/ahci.c    |   91 ++++++++++++++++++++++++++++++++++++--
>>>    drivers/ata/ahci.h    |    6 +++
>>>    drivers/ata/libahci.c |  118
>>> ++++++++++++++++++++++++++++++++++++++++++++++---
>>>    3 files changed, 205 insertions(+), 10 deletions(-)
>>
>>
>> Acked-by: Jeff Garzik <jgarzik@redhat.com>
>>
>> Normally, this amount of changes would -really- need to go through the
>> libata tree.  However, given the amount of dependencies, it either needs a
>> merge tree or to go through the PCI tree...?
>>
>> Any maintainer comments on disposition?
>
> For what it's worth, the bulk of this change is outside PCI, so it
> doesn't seem to me like it should go through the PCI tree.  I think I
> did ack the part that touched PCI, and there's not much activity in
> the PCI MSI area right now, so I'm fine with it going through libata
> or whatever people think makes sense.

That works for me, too.  I'm ready to queue it, if libata tree is fine 
with people.

	Jeff




--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ingo Molnar Oct. 2, 2012, 5:09 a.m. UTC | #4
* Alexander Gordeev <agordeev@redhat.com> wrote:

> Take advantage of multiple MSIs implementation on x86 - on systems with
> IRQ remapping AHCI ports not only get assigned separate MSI vectors -
> but also separate IRQs. As result, interrupts generated by different
> ports could be serviced on different CPUs rather than on a single one.
> 
> In cases when number of allocated MSIs is less than requested the Sharing
> Last MSI mode does not get used, no matter implemented in hardware or not.
> Instead, the driver assumes the advantage of multiple MSIs is negated and
> falls back to the single MSI mode as if MRSM bit was set (some Intel chips
> implement this strategy anyway - MRSM bit gets set even if the number of
> allocated MSIs exceeds the number of implemented ports).
> 
> Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
> ---
>  drivers/ata/ahci.c    |   91 ++++++++++++++++++++++++++++++++++++--
>  drivers/ata/ahci.h    |    6 +++
>  drivers/ata/libahci.c |  118 ++++++++++++++++++++++++++++++++++++++++++++++---
>  3 files changed, 205 insertions(+), 10 deletions(-)
> 
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index 7862d17..5463fcea 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -1057,6 +1057,84 @@ static inline void ahci_gtf_filter_workaround(struct ata_host *host)
>  {}
>  #endif
>  
> +int ahci_init_interrupts(struct pci_dev *pdev, struct ahci_host_priv *hpriv)
> +{
> +	int rc;
> +	unsigned int maxvec;
> +
> +	if (!(hpriv->flags & AHCI_HFLAG_NO_MSI)) {
> +		rc = pci_enable_msi_block_auto(pdev, &maxvec);
> +		if (rc > 0) {
> +			if ((rc == maxvec) || (rc == 1))
> +				return rc;
> +			/* assume that advantage of multipe MSIs is negated,
> +			 * so fallback to single MSI mode to save resources */

Please use the customary (multi-line) comment style:

  /*
   * Comment .....
   * ...... goes here.
   */

specified in Documentation/CodingStyle.

> +			pci_disable_msi(pdev);
> +			if (!pci_enable_msi(pdev))
> +				return 1;
> +		}
> +	}
> +
> +	pci_intx(pdev, 1);
> +	return 0;
> +}
> +
> +/**
> + *	ahci_host_activate - start AHCI host, request IRQs and register it
> + *	@host: target ATA host
> + *	@irq: base IRQ number to request
> + *	@n_msis: number of MSIs allocated for this host
> + *	@irq_handler: irq_handler used when requesting IRQs
> + *	@irq_flags: irq_flags used when requesting IRQs
> + *
> + *	Similar to ata_host_activate, but requests IRQs according to AHCI-1.1
> + *	when multiple MSIs were allocated. That is one MSI per port, starting
> + *	from @irq.
> + *
> + *	LOCKING:
> + *	Inherited from calling layer (may sleep).
> + *
> + *	RETURNS:
> + *	0 on success, -errno otherwise.
> + */
> +int ahci_host_activate(struct ata_host *host, int irq, unsigned int n_msis)
> +{
> +	int i, rc;
> +
> +	/* Sharing Last Message among several ports is not supported */
> +	if (n_msis < host->n_ports)
> +		return -EINVAL;
> +
> +	rc = ata_host_start(host);
> +	if (rc)
> +		return rc;
> +
> +	for (i = 0; i < host->n_ports; i++) {
> +		rc = devm_request_threaded_irq(host->dev,
> +			irq + i, ahci_hw_interrupt, ahci_thread_fn, IRQF_SHARED,
> +			dev_driver_string(host->dev), host->ports[i]);
> +		if (rc)
> +			goto out_free_irqs;
> +	}
> +
> +	for (i = 0; i < host->n_ports; i++)
> +		ata_port_desc(host->ports[i], "irq %d", irq + i);
> +
> +	rc = ata_host_register(host, &ahci_sht);
> +	if (rc)
> +		goto out_free_all_irqs;
> +
> +	return 0;
> +
> +out_free_all_irqs:
> +	i = host->n_ports;
> +out_free_irqs:
> +	for (; i; i--)
> +		devm_free_irq(host->dev, irq + i - 1, host->ports[i]);

Please test the failure path somehow - for example I'm quite 
sure that the line above is buggy and will crash/hang a real 
system: it should be host->ports[i-1].

Writing it as:

		devm_free_irq(host->dev, irq + i-1, host->ports[i-1]);

would help readability as well as bit - or just:

	for (i--; i >= 0; i--)
		devm_free_irq(host->dev, irq + i, host->ports[i]);

(untested)

> +
> +	return rc;
> +}
> +
>  static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
>  {
>  	unsigned int board_id = ent->driver_data;
> @@ -1065,7 +1143,7 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
>  	struct device *dev = &pdev->dev;
>  	struct ahci_host_priv *hpriv;
>  	struct ata_host *host;
> -	int n_ports, i, rc;
> +	int n_ports, n_msis, i, rc;
>  	int ahci_pci_bar = AHCI_PCI_BAR_STANDARD;
>  
>  	VPRINTK("ENTER\n");
> @@ -1150,11 +1228,12 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
>  	if (ahci_sb600_enable_64bit(pdev))
>  		hpriv->flags &= ~AHCI_HFLAG_32BIT_ONLY;
>  
> -	if ((hpriv->flags & AHCI_HFLAG_NO_MSI) || pci_enable_msi(pdev))
> -		pci_intx(pdev, 1);
> -
>  	hpriv->mmio = pcim_iomap_table(pdev)[ahci_pci_bar];
>  
> +	n_msis = ahci_init_interrupts(pdev, hpriv);
> +	if (n_msis > 1)
> +		hpriv->flags |= AHCI_HFLAG_MULTI_MSI;
> +
>  	/* save initial config */
>  	ahci_pci_save_initial_config(pdev, hpriv);
>  
> @@ -1250,6 +1329,10 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
>  	ahci_pci_print_info(host);
>  
>  	pci_set_master(pdev);
> +
> +	if (hpriv->flags & AHCI_HFLAG_MULTI_MSI)
> +		return ahci_host_activate(host, pdev->irq, n_msis);
> +
>  	return ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED,
>  				 &ahci_sht);
>  }
> diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
> index 57eb1c2..24251e8 100644
> --- a/drivers/ata/ahci.h
> +++ b/drivers/ata/ahci.h
> @@ -216,6 +216,7 @@ enum {
>  	AHCI_HFLAG_DELAY_ENGINE		= (1 << 15), /* do not start engine on
>  						        port start (wait until
>  						        error-handling stage) */
> +	AHCI_HFLAG_MULTI_MSI		= (1 << 16), /* multiple PCI MSIs */
>  
>  	/* ap->flags bits */
>  
> @@ -282,6 +283,8 @@ struct ahci_port_priv {
>  	unsigned int		ncq_saw_d2h:1;
>  	unsigned int		ncq_saw_dmas:1;
>  	unsigned int		ncq_saw_sdb:1;
> +	u32			intr_status;	/* interrupts to handle */
> +	spinlock_t		lock;

In general it's nice to add a small comment that explains what 
data the lock protects precisely and in what circumstances it's 
used - especially as it's in the middle of a structure, making 
it unclear at first sight whether it's for the whole ahci_port 
descriptor or just part of it.

>  	u32 			intr_mask;	/* interrupts to enable */
>  	bool			fbs_supported;	/* set iff FBS is supported */
>  	bool			fbs_enabled;	/* set iff FBS is enabled */
> @@ -343,7 +346,10 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv,
>  			  struct ata_port_info *pi);
>  int ahci_reset_em(struct ata_host *host);
>  irqreturn_t ahci_interrupt(int irq, void *dev_instance);
> +irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance);
> +irqreturn_t ahci_thread_fn(int irq, void *dev_instance);
>  void ahci_print_info(struct ata_host *host, const char *scc_s);
> +int ahci_host_activate(struct ata_host *host, int irq, unsigned int n_msis);
>  
>  static inline void __iomem *__ahci_port_base(struct ata_host *host,
>  					     unsigned int port_no)
> diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
> index 555c07a..3b54e84 100644
> --- a/drivers/ata/libahci.c
> +++ b/drivers/ata/libahci.c
> @@ -1639,19 +1639,16 @@ static void ahci_error_intr(struct ata_port *ap, u32 irq_stat)
>  		ata_port_abort(ap);
>  }
>  
> -static void ahci_port_intr(struct ata_port *ap)
> +static void ahci_handle_port_interrupt(struct ata_port *ap,
> +				       void __iomem *port_mmio, u32 status)
>  {
> -	void __iomem *port_mmio = ahci_port_base(ap);
>  	struct ata_eh_info *ehi = &ap->link.eh_info;
>  	struct ahci_port_priv *pp = ap->private_data;
>  	struct ahci_host_priv *hpriv = ap->host->private_data;
>  	int resetting = !!(ap->pflags & ATA_PFLAG_RESETTING);
> -	u32 status, qc_active = 0;
> +	u32 qc_active = 0;
>  	int rc;
>  
> -	status = readl(port_mmio + PORT_IRQ_STAT);
> -	writel(status, port_mmio + PORT_IRQ_STAT);
> -
>  	/* ignore BAD_PMP while resetting */
>  	if (unlikely(resetting))
>  		status &= ~PORT_IRQ_BAD_PMP;
> @@ -1727,6 +1724,107 @@ static void ahci_port_intr(struct ata_port *ap)
>  	}
>  }
>  
> +void ahci_port_intr(struct ata_port *ap)
> +{
> +	void __iomem *port_mmio = ahci_port_base(ap);
> +	u32 status;
> +
> +	status = readl(port_mmio + PORT_IRQ_STAT);
> +	writel(status, port_mmio + PORT_IRQ_STAT);
> +
> +	ahci_handle_port_interrupt(ap, port_mmio, status);
> +}
> +
> +irqreturn_t ahci_thread_fn(int irq, void *dev_instance)
> +{
> +	struct ata_port *ap = dev_instance;
> +	struct ahci_port_priv *pp = ap->private_data;
> +	void __iomem *port_mmio = ahci_port_base(ap);
> +	unsigned long flags;
> +	u32 status;
> +
> +	spin_lock_irqsave(&ap->host->lock, flags);
> +	status = pp->intr_status;
> +	if (status)
> +		pp->intr_status = 0;
> +	spin_unlock_irqrestore(&ap->host->lock, flags);
> +
> +	spin_lock_bh(ap->lock);
> +	ahci_handle_port_interrupt(ap, port_mmio, status);
> +	spin_unlock_bh(ap->lock);
> +
> +	return IRQ_HANDLED;
> +}
> +EXPORT_SYMBOL_GPL(ahci_thread_fn);
> +
> +void ahci_hw_port_interrupt(struct ata_port *ap)
> +{
> +	void __iomem *port_mmio = ahci_port_base(ap);
> +	struct ahci_port_priv *pp = ap->private_data;
> +	u32 status;
> +
> +	status = readl(port_mmio + PORT_IRQ_STAT);
> +	writel(status, port_mmio + PORT_IRQ_STAT);
> +
> +	pp->intr_status |= status;
> +}
> +
> +irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance)
> +{
> +	struct ata_port *ap_this = dev_instance;
> +	struct ahci_port_priv *pp = ap_this->private_data;
> +	struct ata_host *host = ap_this->host;
> +	struct ahci_host_priv *hpriv = host->private_data;
> +	void __iomem *mmio = hpriv->mmio;
> +	unsigned int i;
> +	u32 irq_stat, irq_masked;
> +
> +	VPRINTK("ENTER\n");

Is this per IRQ handler execution debugging code still needed? 
Same for the other VPRINTK() lines in this patch.

Thanks,

	Ingo
--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexander Gordeev Oct. 2, 2012, 4:42 p.m. UTC | #5
On Tue, Oct 02, 2012 at 07:09:29AM +0200, Ingo Molnar wrote:
> > +irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance)
> > +{
> > +	struct ata_port *ap_this = dev_instance;
> > +	struct ahci_port_priv *pp = ap_this->private_data;
> > +	struct ata_host *host = ap_this->host;
> > +	struct ahci_host_priv *hpriv = host->private_data;
> > +	void __iomem *mmio = hpriv->mmio;
> > +	unsigned int i;
> > +	u32 irq_stat, irq_masked;
> > +
> > +	VPRINTK("ENTER\n");
> 
> Is this per IRQ handler execution debugging code still needed? 
> Same for the other VPRINTK() lines in this patch.

These VPRINKs are only to make new handlers look like ahci_interrupt()
which did not change. I believe, if they need to go it is better to
remove them altogether, with a separate followup patch.

Jeff, what do you think?

Thanks!
Jeff Garzik Oct. 2, 2012, 4:45 p.m. UTC | #6
On 10/02/2012 12:42 PM, Alexander Gordeev wrote:
> On Tue, Oct 02, 2012 at 07:09:29AM +0200, Ingo Molnar wrote:
>>> +irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance)
>>> +{
>>> +	struct ata_port *ap_this = dev_instance;
>>> +	struct ahci_port_priv *pp = ap_this->private_data;
>>> +	struct ata_host *host = ap_this->host;
>>> +	struct ahci_host_priv *hpriv = host->private_data;
>>> +	void __iomem *mmio = hpriv->mmio;
>>> +	unsigned int i;
>>> +	u32 irq_stat, irq_masked;
>>> +
>>> +	VPRINTK("ENTER\n");
>>
>> Is this per IRQ handler execution debugging code still needed?
>> Same for the other VPRINTK() lines in this patch.
>
> These VPRINKs are only to make new handlers look like ahci_interrupt()
> which did not change. I believe, if they need to go it is better to
> remove them altogether, with a separate followup patch.

Definitely followup patch material + discussion :)

For the moment, the above is consistent with existing code, and by 
default compiled out, as one would expect.

	Jeff





--
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexander Gordeev Oct. 4, 2012, 2:35 p.m. UTC | #7
On Tue, Oct 02, 2012 at 12:45:26PM -0400, Jeff Garzik wrote:
> >>>+	VPRINTK("ENTER\n");
> >>
> >>Is this per IRQ handler execution debugging code still needed?
> >>Same for the other VPRINTK() lines in this patch.
> >
> >These VPRINKs are only to make new handlers look like ahci_interrupt()
> >which did not change. I believe, if they need to go it is better to
> >remove them altogether, with a separate followup patch.
> 
> Definitely followup patch material + discussion :)

It appears the discussion comes first :)

As DPRINTKs/VPRINTKs are all over the ATA stack, I do not really realize
why it should be eliminated in one places (i.e. in hardware handlers) and
left in anothers. So as long as you would not like to remove them all, I
would leave it as is for now.

Does it make sense for you?
diff mbox

Patch

diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
index 7862d17..5463fcea 100644
--- a/drivers/ata/ahci.c
+++ b/drivers/ata/ahci.c
@@ -1057,6 +1057,84 @@  static inline void ahci_gtf_filter_workaround(struct ata_host *host)
 {}
 #endif
 
+int ahci_init_interrupts(struct pci_dev *pdev, struct ahci_host_priv *hpriv)
+{
+	int rc;
+	unsigned int maxvec;
+
+	if (!(hpriv->flags & AHCI_HFLAG_NO_MSI)) {
+		rc = pci_enable_msi_block_auto(pdev, &maxvec);
+		if (rc > 0) {
+			if ((rc == maxvec) || (rc == 1))
+				return rc;
+			/* assume that advantage of multipe MSIs is negated,
+			 * so fallback to single MSI mode to save resources */
+			pci_disable_msi(pdev);
+			if (!pci_enable_msi(pdev))
+				return 1;
+		}
+	}
+
+	pci_intx(pdev, 1);
+	return 0;
+}
+
+/**
+ *	ahci_host_activate - start AHCI host, request IRQs and register it
+ *	@host: target ATA host
+ *	@irq: base IRQ number to request
+ *	@n_msis: number of MSIs allocated for this host
+ *	@irq_handler: irq_handler used when requesting IRQs
+ *	@irq_flags: irq_flags used when requesting IRQs
+ *
+ *	Similar to ata_host_activate, but requests IRQs according to AHCI-1.1
+ *	when multiple MSIs were allocated. That is one MSI per port, starting
+ *	from @irq.
+ *
+ *	LOCKING:
+ *	Inherited from calling layer (may sleep).
+ *
+ *	RETURNS:
+ *	0 on success, -errno otherwise.
+ */
+int ahci_host_activate(struct ata_host *host, int irq, unsigned int n_msis)
+{
+	int i, rc;
+
+	/* Sharing Last Message among several ports is not supported */
+	if (n_msis < host->n_ports)
+		return -EINVAL;
+
+	rc = ata_host_start(host);
+	if (rc)
+		return rc;
+
+	for (i = 0; i < host->n_ports; i++) {
+		rc = devm_request_threaded_irq(host->dev,
+			irq + i, ahci_hw_interrupt, ahci_thread_fn, IRQF_SHARED,
+			dev_driver_string(host->dev), host->ports[i]);
+		if (rc)
+			goto out_free_irqs;
+	}
+
+	for (i = 0; i < host->n_ports; i++)
+		ata_port_desc(host->ports[i], "irq %d", irq + i);
+
+	rc = ata_host_register(host, &ahci_sht);
+	if (rc)
+		goto out_free_all_irqs;
+
+	return 0;
+
+out_free_all_irqs:
+	i = host->n_ports;
+out_free_irqs:
+	for (; i; i--)
+		devm_free_irq(host->dev, irq + i - 1, host->ports[i]);
+
+	return rc;
+}
+
 static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
 {
 	unsigned int board_id = ent->driver_data;
@@ -1065,7 +1143,7 @@  static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
 	struct device *dev = &pdev->dev;
 	struct ahci_host_priv *hpriv;
 	struct ata_host *host;
-	int n_ports, i, rc;
+	int n_ports, n_msis, i, rc;
 	int ahci_pci_bar = AHCI_PCI_BAR_STANDARD;
 
 	VPRINTK("ENTER\n");
@@ -1150,11 +1228,12 @@  static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
 	if (ahci_sb600_enable_64bit(pdev))
 		hpriv->flags &= ~AHCI_HFLAG_32BIT_ONLY;
 
-	if ((hpriv->flags & AHCI_HFLAG_NO_MSI) || pci_enable_msi(pdev))
-		pci_intx(pdev, 1);
-
 	hpriv->mmio = pcim_iomap_table(pdev)[ahci_pci_bar];
 
+	n_msis = ahci_init_interrupts(pdev, hpriv);
+	if (n_msis > 1)
+		hpriv->flags |= AHCI_HFLAG_MULTI_MSI;
+
 	/* save initial config */
 	ahci_pci_save_initial_config(pdev, hpriv);
 
@@ -1250,6 +1329,10 @@  static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
 	ahci_pci_print_info(host);
 
 	pci_set_master(pdev);
+
+	if (hpriv->flags & AHCI_HFLAG_MULTI_MSI)
+		return ahci_host_activate(host, pdev->irq, n_msis);
+
 	return ata_host_activate(host, pdev->irq, ahci_interrupt, IRQF_SHARED,
 				 &ahci_sht);
 }
diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h
index 57eb1c2..24251e8 100644
--- a/drivers/ata/ahci.h
+++ b/drivers/ata/ahci.h
@@ -216,6 +216,7 @@  enum {
 	AHCI_HFLAG_DELAY_ENGINE		= (1 << 15), /* do not start engine on
 						        port start (wait until
 						        error-handling stage) */
+	AHCI_HFLAG_MULTI_MSI		= (1 << 16), /* multiple PCI MSIs */
 
 	/* ap->flags bits */
 
@@ -282,6 +283,8 @@  struct ahci_port_priv {
 	unsigned int		ncq_saw_d2h:1;
 	unsigned int		ncq_saw_dmas:1;
 	unsigned int		ncq_saw_sdb:1;
+	u32			intr_status;	/* interrupts to handle */
+	spinlock_t		lock;
 	u32 			intr_mask;	/* interrupts to enable */
 	bool			fbs_supported;	/* set iff FBS is supported */
 	bool			fbs_enabled;	/* set iff FBS is enabled */
@@ -343,7 +346,10 @@  void ahci_set_em_messages(struct ahci_host_priv *hpriv,
 			  struct ata_port_info *pi);
 int ahci_reset_em(struct ata_host *host);
 irqreturn_t ahci_interrupt(int irq, void *dev_instance);
+irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance);
+irqreturn_t ahci_thread_fn(int irq, void *dev_instance);
 void ahci_print_info(struct ata_host *host, const char *scc_s);
+int ahci_host_activate(struct ata_host *host, int irq, unsigned int n_msis);
 
 static inline void __iomem *__ahci_port_base(struct ata_host *host,
 					     unsigned int port_no)
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 555c07a..3b54e84 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -1639,19 +1639,16 @@  static void ahci_error_intr(struct ata_port *ap, u32 irq_stat)
 		ata_port_abort(ap);
 }
 
-static void ahci_port_intr(struct ata_port *ap)
+static void ahci_handle_port_interrupt(struct ata_port *ap,
+				       void __iomem *port_mmio, u32 status)
 {
-	void __iomem *port_mmio = ahci_port_base(ap);
 	struct ata_eh_info *ehi = &ap->link.eh_info;
 	struct ahci_port_priv *pp = ap->private_data;
 	struct ahci_host_priv *hpriv = ap->host->private_data;
 	int resetting = !!(ap->pflags & ATA_PFLAG_RESETTING);
-	u32 status, qc_active = 0;
+	u32 qc_active = 0;
 	int rc;
 
-	status = readl(port_mmio + PORT_IRQ_STAT);
-	writel(status, port_mmio + PORT_IRQ_STAT);
-
 	/* ignore BAD_PMP while resetting */
 	if (unlikely(resetting))
 		status &= ~PORT_IRQ_BAD_PMP;
@@ -1727,6 +1724,107 @@  static void ahci_port_intr(struct ata_port *ap)
 	}
 }
 
+void ahci_port_intr(struct ata_port *ap)
+{
+	void __iomem *port_mmio = ahci_port_base(ap);
+	u32 status;
+
+	status = readl(port_mmio + PORT_IRQ_STAT);
+	writel(status, port_mmio + PORT_IRQ_STAT);
+
+	ahci_handle_port_interrupt(ap, port_mmio, status);
+}
+
+irqreturn_t ahci_thread_fn(int irq, void *dev_instance)
+{
+	struct ata_port *ap = dev_instance;
+	struct ahci_port_priv *pp = ap->private_data;
+	void __iomem *port_mmio = ahci_port_base(ap);
+	unsigned long flags;
+	u32 status;
+
+	spin_lock_irqsave(&ap->host->lock, flags);
+	status = pp->intr_status;
+	if (status)
+		pp->intr_status = 0;
+	spin_unlock_irqrestore(&ap->host->lock, flags);
+
+	spin_lock_bh(ap->lock);
+	ahci_handle_port_interrupt(ap, port_mmio, status);
+	spin_unlock_bh(ap->lock);
+
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(ahci_thread_fn);
+
+void ahci_hw_port_interrupt(struct ata_port *ap)
+{
+	void __iomem *port_mmio = ahci_port_base(ap);
+	struct ahci_port_priv *pp = ap->private_data;
+	u32 status;
+
+	status = readl(port_mmio + PORT_IRQ_STAT);
+	writel(status, port_mmio + PORT_IRQ_STAT);
+
+	pp->intr_status |= status;
+}
+
+irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance)
+{
+	struct ata_port *ap_this = dev_instance;
+	struct ahci_port_priv *pp = ap_this->private_data;
+	struct ata_host *host = ap_this->host;
+	struct ahci_host_priv *hpriv = host->private_data;
+	void __iomem *mmio = hpriv->mmio;
+	unsigned int i;
+	u32 irq_stat, irq_masked;
+
+	VPRINTK("ENTER\n");
+
+	spin_lock(&host->lock);
+
+	irq_stat = readl(mmio + HOST_IRQ_STAT);
+
+	if (!irq_stat) {
+		u32 status = pp->intr_status;
+
+		spin_unlock(&host->lock);
+
+		VPRINTK("EXIT\n");
+
+		return status ? IRQ_WAKE_THREAD : IRQ_NONE;
+	}
+
+	irq_masked = irq_stat & hpriv->port_map;
+
+	for (i = 0; i < host->n_ports; i++) {
+		struct ata_port *ap;
+
+		if (!(irq_masked & (1 << i)))
+			continue;
+
+		ap = host->ports[i];
+		if (ap) {
+			ahci_hw_port_interrupt(ap);
+			VPRINTK("port %u\n", i);
+		} else {
+			VPRINTK("port %u (no irq)\n", i);
+			if (ata_ratelimit())
+				dev_warn(host->dev,
+					 "interrupt on disabled port %u\n", i);
+		}
+	}
+
+	writel(irq_stat, mmio + HOST_IRQ_STAT);
+
+	spin_unlock(&host->lock);
+
+	VPRINTK("EXIT\n");
+
+	return IRQ_WAKE_THREAD;
+}
+EXPORT_SYMBOL_GPL(ahci_hw_interrupt);
+
 irqreturn_t ahci_interrupt(int irq, void *dev_instance)
 {
 	struct ata_host *host = dev_instance;
@@ -2105,6 +2203,14 @@  static int ahci_port_start(struct ata_port *ap)
 	 */
 	pp->intr_mask = DEF_PORT_IRQ;
 
+	/*
+	 * Switch to per-port locking in case each port has its own MSI vector.
+	 */
+	if ((hpriv->flags & AHCI_HFLAG_MULTI_MSI)) {
+		spin_lock_init(&pp->lock);
+		ap->lock = &pp->lock;
+	}
+
 	ap->private_data = pp;
 
 	/* engage engines, captain */