Re: [PATCH v3 -tip 5/5] AHCI: Support multiple MSIs
From: Ingo Molnar
Date: Tue Oct 02 2012 - 01:09:28 EST
* Alexander Gordeev <agordeev@xxxxxxxxxx> 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@xxxxxxxxxx>
> ---
> 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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/