[PATCH] ahci: convert to new EH
authorTejun Heo <htejun@gmail.com>
Mon, 15 May 2006 11:58:29 +0000 (20:58 +0900)
committerTejun Heo <htejun@gmail.com>
Mon, 15 May 2006 11:58:29 +0000 (20:58 +0900)
Convert AHCI to new EH.  Unfortunately, ICH7 AHCI reacts badly if IRQ
mask is diddled during operation.  So, freezing is implemented by
unconditionally clearing interrupt conditions while frozen.

* Interrupts are categorized according to required action.
  e.g. Connection status or unknown FIS error requires freezing the
  port while TF or HBUS_DATA don't.

* Only CONNECT (reflects SErr.X) interrupt is taken into account not
  PHYRDY (SErr.N), as CONNECT is better cue for starting EH.

* AHCI may be invoked without any active command.  e.g. CONNECT irq
  occuring while no qc in progress still triggers EH and will reset
  the port and revalidate attached device.

Signed-off-by: Tejun Heo <htejun@gmail.com>
drivers/scsi/ahci.c

index a4fb8d0..d6894bd 100644 (file)
@@ -71,6 +71,7 @@ enum {
        AHCI_CMD_CLR_BUSY       = (1 << 10),
 
        RX_FIS_D2H_REG          = 0x40, /* offset of D2H Register FIS data */
+       RX_FIS_UNK              = 0x60, /* offset of Unknown FIS data */
 
        board_ahci              = 0,
        board_ahci_vt8251       = 1,
@@ -128,15 +129,16 @@ enum {
        PORT_IRQ_PIOS_FIS       = (1 << 1), /* PIO Setup FIS rx'd */
        PORT_IRQ_D2H_REG_FIS    = (1 << 0), /* D2H Register FIS rx'd */
 
-       PORT_IRQ_FATAL          = PORT_IRQ_TF_ERR |
-                                 PORT_IRQ_HBUS_ERR |
-                                 PORT_IRQ_HBUS_DATA_ERR |
-                                 PORT_IRQ_IF_ERR,
-       DEF_PORT_IRQ            = PORT_IRQ_FATAL | PORT_IRQ_PHYRDY |
-                                 PORT_IRQ_CONNECT | PORT_IRQ_SG_DONE |
-                                 PORT_IRQ_UNK_FIS | PORT_IRQ_SDB_FIS |
-                                 PORT_IRQ_DMAS_FIS | PORT_IRQ_PIOS_FIS |
-                                 PORT_IRQ_D2H_REG_FIS,
+       PORT_IRQ_FREEZE         = PORT_IRQ_HBUS_ERR |
+                                 PORT_IRQ_IF_ERR |
+                                 PORT_IRQ_CONNECT |
+                                 PORT_IRQ_UNK_FIS,
+       PORT_IRQ_ERROR          = PORT_IRQ_FREEZE |
+                                 PORT_IRQ_TF_ERR |
+                                 PORT_IRQ_HBUS_DATA_ERR,
+       DEF_PORT_IRQ            = PORT_IRQ_ERROR | PORT_IRQ_SG_DONE |
+                                 PORT_IRQ_SDB_FIS | PORT_IRQ_DMAS_FIS |
+                                 PORT_IRQ_PIOS_FIS | PORT_IRQ_D2H_REG_FIS,
 
        /* PORT_CMD bits */
        PORT_CMD_ATAPI          = (1 << 24), /* Device is ATAPI */
@@ -197,13 +199,15 @@ static unsigned int ahci_qc_issue(struct ata_queued_cmd *qc);
 static irqreturn_t ahci_interrupt (int irq, void *dev_instance, struct pt_regs *regs);
 static int ahci_probe_reset(struct ata_port *ap, unsigned int *classes);
 static void ahci_irq_clear(struct ata_port *ap);
-static void ahci_eng_timeout(struct ata_port *ap);
 static int ahci_port_start(struct ata_port *ap);
 static void ahci_port_stop(struct ata_port *ap);
 static void ahci_tf_read(struct ata_port *ap, struct ata_taskfile *tf);
 static void ahci_qc_prep(struct ata_queued_cmd *qc);
 static u8 ahci_check_status(struct ata_port *ap);
-static inline int ahci_host_intr(struct ata_port *ap, struct ata_queued_cmd *qc);
+static void ahci_freeze(struct ata_port *ap);
+static void ahci_thaw(struct ata_port *ap);
+static void ahci_error_handler(struct ata_port *ap);
+static void ahci_post_internal_cmd(struct ata_queued_cmd *qc);
 static void ahci_remove_one (struct pci_dev *pdev);
 
 static struct scsi_host_template ahci_sht = {
@@ -237,14 +241,18 @@ static const struct ata_port_operations ahci_ops = {
        .qc_prep                = ahci_qc_prep,
        .qc_issue               = ahci_qc_issue,
 
-       .eng_timeout            = ahci_eng_timeout,
-
        .irq_handler            = ahci_interrupt,
        .irq_clear              = ahci_irq_clear,
 
        .scr_read               = ahci_scr_read,
        .scr_write              = ahci_scr_write,
 
+       .freeze                 = ahci_freeze,
+       .thaw                   = ahci_thaw,
+
+       .error_handler          = ahci_error_handler,
+       .post_internal_cmd      = ahci_post_internal_cmd,
+
        .port_start             = ahci_port_start,
        .port_stop              = ahci_port_stop,
 };
@@ -789,108 +797,97 @@ static void ahci_qc_prep(struct ata_queued_cmd *qc)
        ahci_fill_cmd_slot(pp, opts);
 }
 
-static void ahci_restart_port(struct ata_port *ap, u32 irq_stat)
+static void ahci_error_intr(struct ata_port *ap, u32 irq_stat)
 {
-       void __iomem *mmio = ap->host_set->mmio_base;
-       void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
-       u32 tmp;
+       struct ahci_port_priv *pp = ap->private_data;
+       struct ata_eh_info *ehi = &ap->eh_info;
+       unsigned int err_mask = 0, action = 0;
+       struct ata_queued_cmd *qc;
+       u32 serror;
 
-       if ((ap->device[0].class != ATA_DEV_ATAPI) ||
-           ((irq_stat & PORT_IRQ_TF_ERR) == 0))
-               ata_port_printk(ap, KERN_WARNING, "port reset, "
-                      "p_is %x is %x pis %x cmd %x tf %x ss %x se %x\n",
-                       irq_stat,
-                       readl(mmio + HOST_IRQ_STAT),
-                       readl(port_mmio + PORT_IRQ_STAT),
-                       readl(port_mmio + PORT_CMD),
-                       readl(port_mmio + PORT_TFDATA),
-                       readl(port_mmio + PORT_SCR_STAT),
-                       readl(port_mmio + PORT_SCR_ERR));
-
-       /* stop DMA */
-       ahci_stop_engine(ap);
+       ata_ehi_clear_desc(ehi);
 
-       /* clear SATA phy error, if any */
-       tmp = readl(port_mmio + PORT_SCR_ERR);
-       writel(tmp, port_mmio + PORT_SCR_ERR);
+       /* AHCI needs SError cleared; otherwise, it might lock up */
+       serror = ahci_scr_read(ap, SCR_ERROR);
+       ahci_scr_write(ap, SCR_ERROR, serror);
 
-       /* if DRQ/BSY is set, device needs to be reset.
-        * if so, issue COMRESET
-        */
-       tmp = readl(port_mmio + PORT_TFDATA);
-       if (tmp & (ATA_BUSY | ATA_DRQ)) {
-               writel(0x301, port_mmio + PORT_SCR_CTL);
-               readl(port_mmio + PORT_SCR_CTL); /* flush */
-               udelay(10);
-               writel(0x300, port_mmio + PORT_SCR_CTL);
-               readl(port_mmio + PORT_SCR_CTL); /* flush */
+       /* analyze @irq_stat */
+       ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat);
+
+       if (irq_stat & PORT_IRQ_TF_ERR)
+               err_mask |= AC_ERR_DEV;
+
+       if (irq_stat & (PORT_IRQ_HBUS_ERR | PORT_IRQ_HBUS_DATA_ERR)) {
+               err_mask |= AC_ERR_HOST_BUS;
+               action |= ATA_EH_SOFTRESET;
        }
 
-       /* re-start DMA */
-       ahci_start_engine(ap);
-}
+       if (irq_stat & PORT_IRQ_IF_ERR) {
+               err_mask |= AC_ERR_ATA_BUS;
+               action |= ATA_EH_SOFTRESET;
+               ata_ehi_push_desc(ehi, ", interface fatal error");
+       }
 
-static void ahci_eng_timeout(struct ata_port *ap)
-{
-       struct ata_host_set *host_set = ap->host_set;
-       void __iomem *mmio = host_set->mmio_base;
-       void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
-       struct ata_queued_cmd *qc;
-       unsigned long flags;
+       if (irq_stat & (PORT_IRQ_CONNECT | PORT_IRQ_PHYRDY)) {
+               err_mask |= AC_ERR_ATA_BUS;
+               action |= ATA_EH_SOFTRESET;
+               ata_ehi_push_desc(ehi, ", %s", irq_stat & PORT_IRQ_CONNECT ?
+                       "connection status changed" : "PHY RDY changed");
+       }
+
+       if (irq_stat & PORT_IRQ_UNK_FIS) {
+               u32 *unk = (u32 *)(pp->rx_fis + RX_FIS_UNK);
 
-       ata_port_printk(ap, KERN_WARNING, "handling error/timeout\n");
+               err_mask |= AC_ERR_HSM;
+               action |= ATA_EH_SOFTRESET;
+               ata_ehi_push_desc(ehi, ", unknown FIS %08x %08x %08x %08x",
+                                 unk[0], unk[1], unk[2], unk[3]);
+       }
 
-       spin_lock_irqsave(&host_set->lock, flags);
+       /* okay, let's hand over to EH */
+       ehi->serror |= serror;
+       ehi->action |= action;
 
-       ahci_restart_port(ap, readl(port_mmio + PORT_IRQ_STAT));
        qc = ata_qc_from_tag(ap, ap->active_tag);
-       qc->err_mask |= AC_ERR_TIMEOUT;
-
-       spin_unlock_irqrestore(&host_set->lock, flags);
+       if (qc)
+               qc->err_mask |= err_mask;
+       else
+               ehi->err_mask |= err_mask;
 
-       ata_eh_qc_complete(qc);
+       if (irq_stat & PORT_IRQ_FREEZE)
+               ata_port_freeze(ap);
+       else
+               ata_port_abort(ap);
 }
 
-static inline int ahci_host_intr(struct ata_port *ap, struct ata_queued_cmd *qc)
+static void ahci_host_intr(struct ata_port *ap)
 {
        void __iomem *mmio = ap->host_set->mmio_base;
        void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
-       u32 status, serr, ci;
-
-       serr = readl(port_mmio + PORT_SCR_ERR);
-       writel(serr, port_mmio + PORT_SCR_ERR);
+       struct ata_queued_cmd *qc;
+       u32 status, ci;
 
        status = readl(port_mmio + PORT_IRQ_STAT);
        writel(status, port_mmio + PORT_IRQ_STAT);
 
-       ci = readl(port_mmio + PORT_CMD_ISSUE);
-       if (likely((ci & 0x1) == 0)) {
-               if (qc) {
-                       WARN_ON(qc->err_mask);
-                       ata_qc_complete(qc);
-                       qc = NULL;
-               }
+       if (unlikely(status & PORT_IRQ_ERROR)) {
+               ahci_error_intr(ap, status);
+               return;
        }
 
-       if (status & PORT_IRQ_FATAL) {
-               unsigned int err_mask;
-               if (status & PORT_IRQ_TF_ERR)
-                       err_mask = AC_ERR_DEV;
-               else if (status & PORT_IRQ_IF_ERR)
-                       err_mask = AC_ERR_ATA_BUS;
-               else
-                       err_mask = AC_ERR_HOST_BUS;
-
-               /* command processing has stopped due to error; restart */
-               ahci_restart_port(ap, status);
-
-               if (qc) {
-                       qc->err_mask |= err_mask;
+       if ((qc = ata_qc_from_tag(ap, ap->active_tag))) {
+               ci = readl(port_mmio + PORT_CMD_ISSUE);
+               if ((ci & 0x1) == 0) {
                        ata_qc_complete(qc);
+                       return;
                }
        }
 
-       return 1;
+       /* spurious interrupt */
+       if (ata_ratelimit())
+               ata_port_printk(ap, KERN_INFO, "spurious interrupt "
+                               "(irq_stat 0x%x active_tag %d)\n",
+                               status, ap->active_tag);
 }
 
 static void ahci_irq_clear(struct ata_port *ap)
@@ -927,14 +924,7 @@ static irqreturn_t ahci_interrupt (int irq, void *dev_instance, struct pt_regs *
 
                ap = host_set->ports[i];
                if (ap) {
-                       struct ata_queued_cmd *qc;
-                       qc = ata_qc_from_tag(ap, ap->active_tag);
-                       if (!ahci_host_intr(ap, qc))
-                               if (ata_ratelimit())
-                                       dev_printk(KERN_WARNING, host_set->dev,
-                                         "unhandled interrupt on port %u\n",
-                                         i);
-
+                       ahci_host_intr(ap);
                        VPRINTK("port %u\n", i);
                } else {
                        VPRINTK("port %u (no irq)\n", i);
@@ -951,7 +941,7 @@ static irqreturn_t ahci_interrupt (int irq, void *dev_instance, struct pt_regs *
                handled = 1;
        }
 
-        spin_unlock(&host_set->lock);
+       spin_unlock(&host_set->lock);
 
        VPRINTK("EXIT\n");
 
@@ -969,6 +959,56 @@ static unsigned int ahci_qc_issue(struct ata_queued_cmd *qc)
        return 0;
 }
 
+static void ahci_freeze(struct ata_port *ap)
+{
+       void __iomem *mmio = ap->host_set->mmio_base;
+       void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
+
+       /* turn IRQ off */
+       writel(0, port_mmio + PORT_IRQ_MASK);
+}
+
+static void ahci_thaw(struct ata_port *ap)
+{
+       void __iomem *mmio = ap->host_set->mmio_base;
+       void __iomem *port_mmio = ahci_port_base(mmio, ap->port_no);
+       u32 tmp;
+
+       /* clear IRQ */
+       tmp = readl(port_mmio + PORT_IRQ_STAT);
+       writel(tmp, port_mmio + PORT_IRQ_STAT);
+       writel(1 << ap->id, mmio + HOST_IRQ_STAT);
+
+       /* turn IRQ back on */
+       writel(DEF_PORT_IRQ, port_mmio + PORT_IRQ_MASK);
+}
+
+static void ahci_error_handler(struct ata_port *ap)
+{
+       if (!(ap->flags & ATA_FLAG_FROZEN)) {
+               /* restart engine */
+               ahci_stop_engine(ap);
+               ahci_start_engine(ap);
+       }
+
+       /* perform recovery */
+       ata_do_eh(ap, ahci_softreset, ahci_hardreset, ahci_postreset);
+}
+
+static void ahci_post_internal_cmd(struct ata_queued_cmd *qc)
+{
+       struct ata_port *ap = qc->ap;
+
+       if (qc->flags & ATA_QCFLAG_FAILED)
+               qc->err_mask |= AC_ERR_OTHER;
+
+       if (qc->err_mask) {
+               /* make DMA engine forget about the failed command */
+               ahci_stop_engine(ap);
+               ahci_start_engine(ap);
+       }
+}
+
 static void ahci_setup_port(struct ata_ioports *port, unsigned long base,
                            unsigned int port_idx)
 {
@@ -1113,9 +1153,6 @@ static int ahci_host_init(struct ata_probe_ent *probe_ent)
                        writel(tmp, port_mmio + PORT_IRQ_STAT);
 
                writel(1 << i, mmio + HOST_IRQ_STAT);
-
-               /* set irq mask (enables interrupts) */
-               writel(DEF_PORT_IRQ, port_mmio + PORT_IRQ_MASK);
        }
 
        tmp = readl(mmio + HOST_CTL);