diff options
Diffstat (limited to 'drivers/ata/libahci.c')
-rw-r--r-- | drivers/ata/libahci.c | 195 |
1 files changed, 130 insertions, 65 deletions
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index b784e9de426..5eb61c9e63d 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -1778,30 +1778,28 @@ static void ahci_handle_port_interrupt(struct ata_port *ap, } } -static void ahci_port_intr(struct ata_port *ap) +static void ahci_update_intr_status(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); - ahci_handle_port_interrupt(ap, port_mmio, status); + atomic_or(status, &pp->intr_status); } -irqreturn_t ahci_thread_fn(int irq, void *dev_instance) +static irqreturn_t ahci_port_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); + status = atomic_xchg(&pp->intr_status, 0); + if (!status) + return IRQ_NONE; spin_lock_bh(ap->lock); ahci_handle_port_interrupt(ap, port_mmio, status); @@ -1809,47 +1807,13 @@ irqreturn_t ahci_thread_fn(int irq, void *dev_instance) return IRQ_HANDLED; } -EXPORT_SYMBOL_GPL(ahci_thread_fn); - -static 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) +irqreturn_t ahci_thread_fn(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 ata_host *host = dev_instance; struct ahci_host_priv *hpriv = host->private_data; - void __iomem *mmio = hpriv->mmio; + u32 irq_masked = hpriv->port_map; 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; @@ -1859,7 +1823,7 @@ irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance) ap = host->ports[i]; if (ap) { - ahci_hw_port_interrupt(ap); + ahci_port_thread_fn(irq, ap); VPRINTK("port %u\n", i); } else { VPRINTK("port %u (no irq)\n", i); @@ -1869,17 +1833,29 @@ irqreturn_t ahci_hw_interrupt(int irq, void *dev_instance) } } - writel(irq_stat, mmio + HOST_IRQ_STAT); + return IRQ_HANDLED; +} - spin_unlock(&host->lock); +static irqreturn_t ahci_multi_irqs_intr(int irq, void *dev_instance) +{ + struct ata_port *ap = dev_instance; + void __iomem *port_mmio = ahci_port_base(ap); + struct ahci_port_priv *pp = ap->private_data; + u32 status; + + VPRINTK("ENTER\n"); + + status = readl(port_mmio + PORT_IRQ_STAT); + writel(status, port_mmio + PORT_IRQ_STAT); + + atomic_or(status, &pp->intr_status); VPRINTK("EXIT\n"); return IRQ_WAKE_THREAD; } -EXPORT_SYMBOL_GPL(ahci_hw_interrupt); -irqreturn_t ahci_interrupt(int irq, void *dev_instance) +static irqreturn_t ahci_single_irq_intr(int irq, void *dev_instance) { struct ata_host *host = dev_instance; struct ahci_host_priv *hpriv; @@ -1899,8 +1875,6 @@ irqreturn_t ahci_interrupt(int irq, void *dev_instance) irq_masked = irq_stat & hpriv->port_map; - spin_lock(&host->lock); - for (i = 0; i < host->n_ports; i++) { struct ata_port *ap; @@ -1909,7 +1883,7 @@ irqreturn_t ahci_interrupt(int irq, void *dev_instance) ap = host->ports[i]; if (ap) { - ahci_port_intr(ap); + ahci_update_intr_status(ap); VPRINTK("port %u\n", i); } else { VPRINTK("port %u (no irq)\n", i); @@ -1932,13 +1906,10 @@ irqreturn_t ahci_interrupt(int irq, void *dev_instance) */ writel(irq_stat, mmio + HOST_IRQ_STAT); - spin_unlock(&host->lock); - VPRINTK("EXIT\n"); - return IRQ_RETVAL(handled); + return handled ? IRQ_WAKE_THREAD : IRQ_NONE; } -EXPORT_SYMBOL_GPL(ahci_interrupt); unsigned int ahci_qc_issue(struct ata_queued_cmd *qc) { @@ -2349,13 +2320,8 @@ 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; - } + spin_lock_init(&pp->lock); + ap->lock = &pp->lock; ap->private_data = pp; @@ -2472,6 +2438,105 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv, } EXPORT_SYMBOL_GPL(ahci_set_em_messages); +static int ahci_host_activate_multi_irqs(struct ata_host *host, int irq, + struct scsi_host_template *sht) +{ + int i, rc; + + rc = ata_host_start(host); + if (rc) + return rc; + + for (i = 0; i < host->n_ports; i++) { + struct ahci_port_priv *pp = host->ports[i]->private_data; + + /* Do not receive interrupts sent by dummy ports */ + if (!pp) { + disable_irq(irq + i); + continue; + } + + rc = devm_request_threaded_irq(host->dev, irq + i, + ahci_multi_irqs_intr, + ahci_port_thread_fn, IRQF_SHARED, + pp->irq_desc, 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, sht); + if (rc) + goto out_free_all_irqs; + + return 0; + +out_free_all_irqs: + i = host->n_ports; +out_free_irqs: + for (i--; i >= 0; i--) + devm_free_irq(host->dev, irq + i, host->ports[i]); + + return rc; +} + +static int ahci_host_activate_single_irq(struct ata_host *host, int irq, + struct scsi_host_template *sht) +{ + int i, rc; + + rc = ata_host_start(host); + if (rc) + return rc; + + rc = devm_request_threaded_irq(host->dev, irq, ahci_single_irq_intr, + ahci_thread_fn, IRQF_SHARED, + dev_driver_string(host->dev), host); + if (rc) + return rc; + + for (i = 0; i < host->n_ports; i++) + ata_port_desc(host->ports[i], "irq %d", irq); + + rc = ata_host_register(host, sht); + if (rc) + devm_free_irq(host->dev, irq, host); + + return rc; +} + +/** + * ahci_host_activate - start AHCI host, request IRQs and register it + * @host: target ATA host + * @irq: base IRQ number to request + * @sht: scsi_host_template to use when registering the host + * + * 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, + struct scsi_host_template *sht) +{ + struct ahci_host_priv *hpriv = host->private_data; + int rc; + + if (hpriv->flags & AHCI_HFLAG_MULTI_MSI) + rc = ahci_host_activate_multi_irqs(host, irq, sht); + else + rc = ahci_host_activate_single_irq(host, irq, sht); + return rc; +} +EXPORT_SYMBOL_GPL(ahci_host_activate); + MODULE_AUTHOR("Jeff Garzik"); MODULE_DESCRIPTION("Common AHCI SATA low-level routines"); MODULE_LICENSE("GPL"); |