[PATCH] ahci: convert to new EH
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>
This commit is contained in:
Родитель
f6aae27ed0
Коммит
78cd52d02f
|
@ -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)
|
||||
{
|
||||
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;
|
||||
|
||||
ata_ehi_clear_desc(ehi);
|
||||
|
||||
/* AHCI needs SError cleared; otherwise, it might lock up */
|
||||
serror = ahci_scr_read(ap, SCR_ERROR);
|
||||
ahci_scr_write(ap, SCR_ERROR, serror);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
/* okay, let's hand over to EH */
|
||||
ehi->serror |= serror;
|
||||
ehi->action |= action;
|
||||
|
||||
qc = ata_qc_from_tag(ap, ap->active_tag);
|
||||
if (qc)
|
||||
qc->err_mask |= err_mask;
|
||||
else
|
||||
ehi->err_mask |= err_mask;
|
||||
|
||||
if (irq_stat & PORT_IRQ_FREEZE)
|
||||
ata_port_freeze(ap);
|
||||
else
|
||||
ata_port_abort(ap);
|
||||
}
|
||||
|
||||
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 tmp;
|
||||
|
||||
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);
|
||||
|
||||
/* clear SATA phy error, if any */
|
||||
tmp = readl(port_mmio + PORT_SCR_ERR);
|
||||
writel(tmp, port_mmio + PORT_SCR_ERR);
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
/* re-start DMA */
|
||||
ahci_start_engine(ap);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ata_port_printk(ap, KERN_WARNING, "handling error/timeout\n");
|
||||
|
||||
spin_lock_irqsave(&host_set->lock, flags);
|
||||
|
||||
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);
|
||||
|
||||
ata_eh_qc_complete(qc);
|
||||
}
|
||||
|
||||
static inline int ahci_host_intr(struct ata_port *ap, struct ata_queued_cmd *qc)
|
||||
{
|
||||
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, ci;
|
||||
|
||||
status = readl(port_mmio + PORT_IRQ_STAT);
|
||||
writel(status, port_mmio + PORT_IRQ_STAT);
|
||||
|
||||
if (unlikely(status & PORT_IRQ_ERROR)) {
|
||||
ahci_error_intr(ap, status);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((qc = ata_qc_from_tag(ap, ap->active_tag))) {
|
||||
ci = readl(port_mmio + PORT_CMD_ISSUE);
|
||||
if (likely((ci & 0x1) == 0)) {
|
||||
if (qc) {
|
||||
WARN_ON(qc->err_mask);
|
||||
if ((ci & 0x1) == 0) {
|
||||
ata_qc_complete(qc);
|
||||
qc = NULL;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче