linux/debian/patches/bugfix/all/libata-ata_piix-clear-spuri...

149 lines
4.3 KiB
Diff

From: Tejun Heo <tj@kernel.org>
Subject: libata,ata_piix: detect and clear spurious IRQs
Patch-Mainline: Backported from changes pending for 2.6.34
References: bnc#445872, bnc#589449
Backport spurious IRQ handling from 2.6.34. It isn't exactly the same
form in that it doesn't use callbacks but implements custom
piix_interrupt() but should function the same. This protects ata_piix
against nobody-cared which gets reported not so rarely.
Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Tejun Heo <teheo@suse.de>
---
drivers/ata/ata_piix.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++-
drivers/ata/libata-sff.c | 12 ++++++-
2 files changed, 85 insertions(+), 3 deletions(-)
Index: linux-2.6.32-SLE11-SP1/drivers/ata/ata_piix.c
===================================================================
--- linux-2.6.32-SLE11-SP1.orig/drivers/ata/ata_piix.c
+++ linux-2.6.32-SLE11-SP1/drivers/ata/ata_piix.c
@@ -949,6 +949,80 @@ static int piix_sidpr_scr_read(struct at
return 0;
}
+static irqreturn_t piix_interrupt(int irq, void *dev_instance)
+{
+ struct ata_host *host = dev_instance;
+ bool retried = false;
+ unsigned int i;
+ unsigned int handled = 0, polling = 0, idle = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+retry:
+ handled = idle = polling = 0;
+ for (i = 0; i < host->n_ports; i++) {
+ struct ata_port *ap = host->ports[i];
+ struct ata_queued_cmd *qc;
+
+ if (ata_port_is_dummy(ap))
+ continue;
+
+ qc = ata_qc_from_tag(ap, ap->link.active_tag);
+ if (qc) {
+ if (!(qc->tf.flags & ATA_TFLAG_POLLING))
+ handled |= ata_sff_host_intr(ap, qc);
+ else
+ polling |= 1 << i;
+ } else
+ idle |= 1 << i;
+ }
+
+ /*
+ * If no port was expecting IRQ but the controller is actually
+ * asserting IRQ line, nobody cared will ensue. Check IRQ
+ * pending status if available and clear spurious IRQ.
+ */
+ if (!handled && !retried) {
+ bool retry = false;
+
+ for (i = 0; i < host->n_ports; i++) {
+ struct ata_port *ap = host->ports[i];
+ u8 host_stat;
+
+ if (polling & (1 << i))
+ continue;
+
+ if (unlikely(!ap->ioaddr.bmdma_addr))
+ continue;
+
+ host_stat = ap->ops->bmdma_status(ap);
+ if (!(host_stat & ATA_DMA_INTR))
+ continue;
+
+ if (idle & (1 << i)) {
+ ap->ops->sff_check_status(ap);
+ ap->ops->sff_irq_clear(ap);
+ } else {
+ /* clear INTRQ and check if BUSY cleared */
+ if (!(ap->ops->sff_check_status(ap) & ATA_BUSY))
+ retry |= true;
+ /*
+ * With command in flight, we can't do
+ * sff_irq_clear() w/o racing with completion.
+ */
+ }
+ }
+ if (retry) {
+ retried = true;
+ goto retry;
+ }
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return IRQ_RETVAL(handled);
+}
+
static int piix_sidpr_scr_write(struct ata_link *link,
unsigned int reg, u32 val)
{
@@ -1607,7 +1681,7 @@ static int __devinit piix_init_one(struc
host->flags |= ATA_HOST_PARALLEL_SCAN;
pci_set_master(pdev);
- return ata_pci_sff_activate_host(host, ata_sff_interrupt, &piix_sht);
+ return ata_pci_sff_activate_host(host, piix_interrupt, &piix_sht);
}
static void piix_remove_one(struct pci_dev *pdev)
Index: linux-2.6.32-SLE11-SP1/drivers/ata/libata-sff.c
===================================================================
--- linux-2.6.32-SLE11-SP1.orig/drivers/ata/libata-sff.c
+++ linux-2.6.32-SLE11-SP1/drivers/ata/libata-sff.c
@@ -1667,6 +1667,7 @@ unsigned int ata_sff_host_intr(struct at
{
struct ata_eh_info *ehi = &ap->link.eh_info;
u8 status, host_stat = 0;
+ bool bmdma_stopped = false;
VPRINTK("ata%u: protocol %d task_state %d\n",
ap->print_id, qc->tf.protocol, ap->hsm_task_state);
@@ -1699,6 +1700,7 @@ unsigned int ata_sff_host_intr(struct at
/* before we do anything else, clear DMA-Start bit */
ap->ops->bmdma_stop(qc);
+ bmdma_stopped = true;
if (unlikely(host_stat & ATA_DMA_ERR)) {
/* error when transfering data to/from memory */
@@ -1716,8 +1718,14 @@ unsigned int ata_sff_host_intr(struct at
/* check main status, clearing INTRQ if needed */
status = ata_sff_irq_status(ap);
- if (status & ATA_BUSY)
- goto idle_irq;
+ if (status & ATA_BUSY) {
+ if (bmdma_stopped) {
+ /* BMDMA engine is already stopped, we're screwed */
+ qc->err_mask |= AC_ERR_HSM;
+ ap->hsm_task_state = HSM_ST_ERR;
+ } else
+ goto idle_irq;
+ }
/* ack bmdma irq events */
ap->ops->sff_irq_clear(ap);