ata: libahci: clear pending interrupt status
authorSzuying Chen <chensiying21@gmail.com>
Thu, 7 Sep 2023 08:17:10 +0000 (16:17 +0800)
committerDamien Le Moal <dlemoal@kernel.org>
Fri, 15 Sep 2023 02:34:31 +0000 (11:34 +0900)
When a CRC error occurs, the HBA asserts an interrupt to indicate an
interface fatal error (PxIS.IFS). The ISR clears PxIE and PxIS, then
does error recovery. If the adapter receives another SDB FIS
with an error (PxIS.TFES) from the device before the start of the EH
recovery process, the interrupt signaling the new SDB cannot be
serviced as PxIE was cleared already. This in turn results in the HBA
inability to issue any command during the error recovery process after
setting PxCMD.ST to 1 because PxIS.TFES is still set.

According to AHCI 1.3.1 specifications section 6.2.2, fatal errors
notified by setting PxIS.HBFS, PxIS.HBDS, PxIS.IFS or PxIS.TFES will
cause the HBA to enter the ERR:Fatal state. In this state, the HBA
shall not issue any new commands.

To avoid this situation, introduce the function
ahci_port_clear_pending_irq() to clear pending interrupts before
executing a COMRESET. This follows the AHCI 1.3.1 - section 6.2.2.2
specification.

Signed-off-by: Szuying Chen <Chloe_Chen@asmedia.com.tw>
Fixes: e0bfd149973d ("[PATCH] ahci: stop engine during hard reset")
Cc: stable@vger.kernel.org
Reviewed-by: Niklas Cassel <niklas.cassel@wdc.com>
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
drivers/ata/libahci.c

index e2baced..f126336 100644 (file)
@@ -1256,6 +1256,26 @@ static ssize_t ahci_activity_show(struct ata_device *dev, char *buf)
        return sprintf(buf, "%d\n", emp->blink_policy);
 }
 
+static void ahci_port_clear_pending_irq(struct ata_port *ap)
+{
+       struct ahci_host_priv *hpriv = ap->host->private_data;
+       void __iomem *port_mmio = ahci_port_base(ap);
+       u32 tmp;
+
+       /* clear SError */
+       tmp = readl(port_mmio + PORT_SCR_ERR);
+       dev_dbg(ap->host->dev, "PORT_SCR_ERR 0x%x\n", tmp);
+       writel(tmp, port_mmio + PORT_SCR_ERR);
+
+       /* clear port IRQ */
+       tmp = readl(port_mmio + PORT_IRQ_STAT);
+       dev_dbg(ap->host->dev, "PORT_IRQ_STAT 0x%x\n", tmp);
+       if (tmp)
+               writel(tmp, port_mmio + PORT_IRQ_STAT);
+
+       writel(1 << ap->port_no, hpriv->mmio + HOST_IRQ_STAT);
+}
+
 static void ahci_port_init(struct device *dev, struct ata_port *ap,
                           int port_no, void __iomem *mmio,
                           void __iomem *port_mmio)
@@ -1270,18 +1290,7 @@ static void ahci_port_init(struct device *dev, struct ata_port *ap,
        if (rc)
                dev_warn(dev, "%s (%d)\n", emsg, rc);
 
-       /* clear SError */
-       tmp = readl(port_mmio + PORT_SCR_ERR);
-       dev_dbg(dev, "PORT_SCR_ERR 0x%x\n", tmp);
-       writel(tmp, port_mmio + PORT_SCR_ERR);
-
-       /* clear port IRQ */
-       tmp = readl(port_mmio + PORT_IRQ_STAT);
-       dev_dbg(dev, "PORT_IRQ_STAT 0x%x\n", tmp);
-       if (tmp)
-               writel(tmp, port_mmio + PORT_IRQ_STAT);
-
-       writel(1 << port_no, mmio + HOST_IRQ_STAT);
+       ahci_port_clear_pending_irq(ap);
 
        /* mark esata ports */
        tmp = readl(port_mmio + PORT_CMD);
@@ -1603,6 +1612,8 @@ int ahci_do_hardreset(struct ata_link *link, unsigned int *class,
        tf.status = ATA_BUSY;
        ata_tf_to_fis(&tf, 0, 0, d2h_fis);
 
+       ahci_port_clear_pending_irq(ap);
+
        rc = sata_link_hardreset(link, timing, deadline, online,
                                 ahci_check_ready);