From: Tejun Heo <htejun@gmail.com>
To: jgarzik@pobox.com, alan@lxorguk.ukuu.org.uk, axboe@suse.de,
albertcc@tw.ibm.com, lkosewsk@gmail.com,
linux-ide@vger.kernel.org
Cc: Tejun Heo <htejun@gmail.com>
Subject: [PATCH 13/14] ahci: convert to new EH
Date: Tue, 11 Apr 2006 22:48:23 +0900 [thread overview]
Message-ID: <114476330353-git-send-email-htejun@gmail.com> (raw)
In-Reply-To: <11447633013561-git-send-email-htejun@gmail.com>
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.
* AHCI interrupt handler does not analyze any of error conditions. It
just records relevant status registers in driver private area and
invoke EH. EH is responsible for decoding all those information.
* 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 | 248 ++++++++++++++++++++++++++++++++-------------------
1 files changed, 154 insertions(+), 94 deletions(-)
29b703131da0ba4391f3e95bd49effc3b0b70d5d
diff --git a/drivers/scsi/ahci.c b/drivers/scsi/ahci.c
index 241ed5d..67950ee 100644
--- a/drivers/scsi/ahci.c
+++ b/drivers/scsi/ahci.c
@@ -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,
@@ -127,15 +128,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 */
@@ -184,6 +186,9 @@ struct ahci_port_priv {
struct ahci_sg *cmd_tbl_sg;
void *rx_fis;
dma_addr_t rx_fis_dma;
+ /* register values stored by interrupt handler for EH */
+ u32 eh_irq_stat;
+ u32 eh_serror;
};
static u32 ahci_scr_read (struct ata_port *ap, unsigned int sc_reg);
@@ -193,13 +198,13 @@ static unsigned int ahci_qc_issue(struct
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 void ahci_error_handler(struct ata_port *ap);
+static void ahci_post_internal_cmd(struct ata_queued_cmd *qc);
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_remove_one (struct pci_dev *pdev);
static struct scsi_host_template ahci_sht = {
@@ -234,7 +239,8 @@ static const struct ata_port_operations
.qc_prep = ahci_qc_prep,
.qc_issue = ahci_qc_issue,
- .eng_timeout = ahci_eng_timeout,
+ .error_handler = ahci_error_handler,
+ .post_internal_cmd = ahci_post_internal_cmd,
.irq_handler = ahci_interrupt,
.irq_clear = ahci_irq_clear,
@@ -757,109 +763,170 @@ static void ahci_qc_prep(struct ata_queu
ahci_fill_cmd_slot(pp, opts);
}
-static void ahci_restart_port(struct ata_port *ap, u32 irq_stat)
+static unsigned int ahci_eh_autopsy(struct ata_port *ap, u32 irq_stat,
+ unsigned int *r_err_mask,
+ char *desc, size_t desc_sz)
{
- 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;
+ unsigned int err_mask = 0, action = 0;
+ int rc;
- if ((ap->device[0].class != ATA_DEV_ATAPI) ||
- ((irq_stat & PORT_IRQ_TF_ERR) == 0))
- printk(KERN_WARNING "ata%u: port reset, "
- "p_is %x is %x pis %x cmd %x tf %x ss %x se %x\n",
- ap->id,
- 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));
+ rc = scnprintf(desc, desc_sz, "irq_stat 0x%08x", irq_stat);
+ desc += rc;
+ desc_sz -= rc;
- /* stop DMA */
- ahci_stop_engine(ap);
+ if (irq_stat & PORT_IRQ_TF_ERR)
+ err_mask |= AC_ERR_DEV;
- /* clear SATA phy error, if any */
- tmp = readl(port_mmio + PORT_SCR_ERR);
- writel(tmp, port_mmio + PORT_SCR_ERR);
+ if (irq_stat & (PORT_IRQ_HBUS_ERR | PORT_IRQ_HBUS_DATA_ERR)) {
+ err_mask |= AC_ERR_HOST_BUS;
+ action |= ATA_PORT_SOFTRESET;
+ }
- /* 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 */
+ if (irq_stat & PORT_IRQ_IF_ERR) {
+ err_mask |= AC_ERR_ATA_BUS;
+ action |= ATA_PORT_SOFTRESET;
+ rc = scnprintf(desc, desc_sz, ", interface fatal error");
+ desc += rc;
+ desc_sz -= rc;
}
- /* re-start DMA */
- ahci_start_engine(ap);
+ if (irq_stat & PORT_IRQ_CONNECT) {
+ err_mask |= AC_ERR_ATA_BUS;
+ action |= ATA_PORT_SOFTRESET;
+ rc = scnprintf(desc, desc_sz, ", connection status changed");
+ desc += rc;
+ desc_sz -= rc;
+ }
+
+ if (irq_stat & PORT_IRQ_UNK_FIS) {
+ u32 *unk = (u32 *)(pp->rx_fis + RX_FIS_UNK);
+
+ err_mask |= AC_ERR_HSM;
+ action |= ATA_PORT_SOFTRESET;
+ rc = scnprintf(desc, desc_sz,
+ ", unknown FIS %08x %08x %08x %08x",
+ unk[0], unk[1], unk[2], unk[3]);
+ desc += rc;
+ desc_sz -= rc;
+ }
+
+ *r_err_mask |= err_mask;
+ return action;
}
-static void ahci_eng_timeout(struct ata_port *ap)
+static void ahci_error_handler(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 ahci_port_priv *pp = ap->private_data;
+ unsigned int action = 0;
+ unsigned int err_mask = 0;
+ unsigned flags;
+ u32 irq_stat, serror;
+ struct ata_taskfile tf;
struct ata_queued_cmd *qc;
- unsigned long flags;
+ char desc[70] = "";
+
+ /* fetch & clear error information from interrupt handler */
+ spin_lock_irqsave(&ap->host_set->lock, flags);
+
+ irq_stat = pp->eh_irq_stat;
+ serror = pp->eh_serror;
+ pp->eh_irq_stat = 0;
+ pp->eh_serror = 0;
- printk(KERN_WARNING "ata%u: handling error/timeout\n", ap->id);
+ spin_unlock_irqrestore(&ap->host_set->lock, flags);
- spin_lock_irqsave(&host_set->lock, flags);
+ if (!(ap->flags & ATA_FLAG_FROZEN)) {
+ /* restart engine */
+ ahci_stop_engine(ap);
+ ahci_start_engine(ap);
+ }
+
+ /* perform recovery */
+ action |= ahci_eh_autopsy(ap, irq_stat, &err_mask, desc, sizeof(desc));
+
+ qc = ata_eh_determine_qc(ap, &tf);
+ if (qc)
+ qc->err_mask |= err_mask;
+
+ action |= ata_eh_autopsy(ap, qc, &tf, serror);
+ ata_eh_report(ap, qc, &tf, serror, action, desc);
+ ata_eh_revive(ap, action,
+ ahci_softreset, ahci_hardreset, ahci_postreset);
+ ata_eh_finish_qcs(ap, qc, &tf);
+}
- 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;
+static void ahci_post_internal_cmd(struct ata_queued_cmd *qc)
+{
+ struct ata_port *ap = qc->ap;
- spin_unlock_irqrestore(&host_set->lock, flags);
+ if (qc->flags & ATA_QCFLAG_FAILED)
+ qc->err_mask |= AC_ERR_OTHER;
- ata_eh_qc_complete(qc);
+ if (qc->err_mask) {
+ /* make DMA engine forget about the failed command */
+ ahci_stop_engine(ap);
+ ahci_start_engine(ap);
+ }
}
-static inline int ahci_host_intr(struct ata_port *ap, struct ata_queued_cmd *qc)
+static inline void ahci_host_intr(struct ata_port *ap)
{
+ struct ahci_port_priv *pp = ap->private_data;
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);
+ u32 status, serror, ci;
+ unsigned int eh_flags;
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;
- }
+ /* AHCI gets unhappy if IRQ mask is diddled with while the
+ * port is active, so we cannot disable IRQ when freezing.
+ * Clear IRQ conditions and hope screaming IRQs don't happen.
+ */
+ if (ap->flags & ATA_FLAG_FROZEN) {
+ /* some AHCI errors hang the controller until SError
+ * is cleared. Store and clear it.
+ */
+ serror = scr_read(ap, SCR_ERROR);
+ scr_write(ap, SCR_ERROR, serror);
+ pp->eh_irq_stat |= status;
+ pp->eh_serror |= serror;
+ 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;
- ata_qc_complete(qc);
+ if (!(status & PORT_IRQ_ERROR)) {
+ struct ata_queued_cmd *qc;
+
+ 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;
+ }
}
+
+ if (ata_ratelimit())
+ printk(KERN_INFO "ata%u: spurious interrupt "
+ "(irq_stat 0x%x active_tag %d)\n",
+ ap->id, status, ap->active_tag);
+
+ return;
}
- return 1;
+ /* Something weird is going on. Hand over to EH. */
+ serror = scr_read(ap, SCR_ERROR);
+ scr_write(ap, SCR_ERROR, serror);
+
+ pp->eh_irq_stat = status;
+ pp->eh_serror = serror;
+
+ eh_flags = ATA_EH_ABORT;
+ if (status & PORT_IRQ_FREEZE)
+ eh_flags |= ATA_EH_FREEZE;
+
+ ata_eh_schedule_port(ap, eh_flags);
}
static void ahci_irq_clear(struct ata_port *ap)
@@ -896,14 +963,7 @@ static irqreturn_t ahci_interrupt (int i
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);
@@ -920,7 +980,7 @@ static irqreturn_t ahci_interrupt (int i
handled = 1;
}
- spin_unlock(&host_set->lock);
+ spin_unlock(&host_set->lock);
VPRINTK("EXIT\n");
--
1.2.4
next prev parent reply other threads:[~2006-04-11 13:48 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-04-11 13:48 [PATCHSET 6/9] new EH implementation, take 2 Tejun Heo
2006-04-11 13:48 ` [PATCH 03/14] libata-eh: add per-dev ata_ering Tejun Heo
2006-04-11 13:48 ` [PATCH 07/14] libata-eh: implement ata_eh_report() Tejun Heo
2006-04-11 13:48 ` [PATCH 11/14] ata_piix: convert to new EH Tejun Heo
2006-04-11 13:48 ` [PATCH 09/14] libata-eh: implement ata_eh_finish_qcs() Tejun Heo
2006-04-11 13:48 ` [PATCH 02/14] libata-eh: implement ata_ering Tejun Heo
2006-04-11 13:48 ` [PATCH 01/14] libata-eh: add constants and flags to be used by EH Tejun Heo
2006-04-11 13:48 ` [PATCH 04/14] libata-eh: implement EH utility functions Tejun Heo
2006-04-11 13:48 ` [PATCH 08/14] libata-eh: implement ata_eh_revive() Tejun Heo
2006-04-19 9:08 ` zhao, forrest
2006-04-19 10:33 ` Tejun Heo
2006-04-11 13:48 ` [PATCH 10/14] libata-eh: implement EH methods for BMDMA controllers Tejun Heo
2006-04-11 13:48 ` [PATCH 05/14] libata-eh: implement ata_eh_determine_qc() Tejun Heo
2006-04-11 13:48 ` [PATCH 06/14] libata-eh: implement ata_eh_autopsy() Tejun Heo
2006-04-11 13:48 ` [PATCH 14/14] sata_sil24: convert to new EH Tejun Heo
2006-04-11 13:48 ` Tejun Heo [this message]
2006-04-20 6:01 ` [PATCH 13/14] ahci: " zhao, forrest
2006-04-20 7:11 ` Tejun Heo
2006-04-20 7:44 ` Jeff Garzik
2006-04-21 1:34 ` Tejun Heo
2006-04-20 9:26 ` zhao, forrest
2006-04-21 1:20 ` Tejun Heo
2006-04-11 13:48 ` [PATCH 12/14] sata_sil: " Tejun Heo
2006-04-27 9:16 ` [PATCHSET 6/9] new EH implementation, take 2 Jeff Garzik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=114476330353-git-send-email-htejun@gmail.com \
--to=htejun@gmail.com \
--cc=alan@lxorguk.ukuu.org.uk \
--cc=albertcc@tw.ibm.com \
--cc=axboe@suse.de \
--cc=jgarzik@pobox.com \
--cc=linux-ide@vger.kernel.org \
--cc=lkosewsk@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).