Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 88ce7550 authored by Tejun Heo's avatar Tejun Heo
Browse files

[PATCH] sata_sil24: convert to new EH



Convert sata_sil24 to new EH.

* When port is frozen, IRQ for the port is masked.

* sil24_softreset() doesn't need to mangle with IRQ mask anymore.
  libata ensures that the port is frozen during reset.

* Only turn on interrupts which are handled by interrupt handler and
  EH.  As we don't handle SDB notify yet, turn it off. DEV_XCHG and
  UNK_FIS are handled by EH and thus turned on.

* sil24_softreset() usually fails to recover the port after DEV_XCHG.
  ATA_PORT_HARDRESET is used as recovery action for DEV_XCHG.

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

Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
parent 2a3917a8
Loading
Loading
Loading
Loading
+187 −141
Original line number Diff line number Diff line
@@ -156,6 +156,9 @@ enum {
	PORT_IRQ_HANDSHAKE	= (1 << 10), /* handshake error threshold */
	PORT_IRQ_SDB_NOTIFY	= (1 << 11), /* SDB notify received */

	DEF_PORT_IRQ		= PORT_IRQ_COMPLETE | PORT_IRQ_ERROR |
				  PORT_IRQ_DEV_XCHG | PORT_IRQ_UNK_FIS,

	/* bits[27:16] are unmasked (raw) */
	PORT_IRQ_RAW_SHIFT	= 16,
	PORT_IRQ_MASKED_MASK	= 0x7ff,
@@ -242,6 +245,58 @@ union sil24_cmd_block {
	struct sil24_atapi_block atapi;
};

static struct sil24_cerr_info {
	unsigned int err_mask, action;
	const char *desc;
} sil24_cerr_db[] = {
	[0]			= { AC_ERR_DEV, ATA_EH_REVALIDATE,
				    "device error" },
	[PORT_CERR_DEV]		= { AC_ERR_DEV, ATA_EH_REVALIDATE,
				    "device error via D2H FIS" },
	[PORT_CERR_SDB]		= { AC_ERR_DEV, ATA_EH_REVALIDATE,
				    "device error via SDB FIS" },
	[PORT_CERR_DATA]	= { AC_ERR_ATA_BUS, ATA_EH_SOFTRESET,
				    "error in data FIS" },
	[PORT_CERR_SEND]	= { AC_ERR_ATA_BUS, ATA_EH_SOFTRESET,
				    "failed to transmit command FIS" },
	[PORT_CERR_INCONSISTENT] = { AC_ERR_HSM, ATA_EH_SOFTRESET,
				     "protocol mismatch" },
	[PORT_CERR_DIRECTION]	= { AC_ERR_HSM, ATA_EH_SOFTRESET,
				    "data directon mismatch" },
	[PORT_CERR_UNDERRUN]	= { AC_ERR_HSM, ATA_EH_SOFTRESET,
				    "ran out of SGEs while writing" },
	[PORT_CERR_OVERRUN]	= { AC_ERR_HSM, ATA_EH_SOFTRESET,
				    "ran out of SGEs while reading" },
	[PORT_CERR_PKT_PROT]	= { AC_ERR_HSM, ATA_EH_SOFTRESET,
				    "invalid data directon for ATAPI CDB" },
	[PORT_CERR_SGT_BOUNDARY] = { AC_ERR_SYSTEM, ATA_EH_SOFTRESET,
				     "SGT no on qword boundary" },
	[PORT_CERR_SGT_TGTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI target abort while fetching SGT" },
	[PORT_CERR_SGT_MSTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI master abort while fetching SGT" },
	[PORT_CERR_SGT_PCIPERR]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI parity error while fetching SGT" },
	[PORT_CERR_CMD_BOUNDARY] = { AC_ERR_SYSTEM, ATA_EH_SOFTRESET,
				     "PRB not on qword boundary" },
	[PORT_CERR_CMD_TGTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI target abort while fetching PRB" },
	[PORT_CERR_CMD_MSTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI master abort while fetching PRB" },
	[PORT_CERR_CMD_PCIPERR]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI parity error while fetching PRB" },
	[PORT_CERR_XFR_UNDEF]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "undefined error while transferring data" },
	[PORT_CERR_XFR_TGTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI target abort while transferring data" },
	[PORT_CERR_XFR_MSTABRT]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI master abort while transferring data" },
	[PORT_CERR_XFR_PCIPERR]	= { AC_ERR_HOST_BUS, ATA_EH_SOFTRESET,
				    "PCI parity error while transferring data" },
	[PORT_CERR_SENDSERVICE]	= { AC_ERR_HSM, ATA_EH_SOFTRESET,
				    "FIS received while sending service FIS" },
};

/*
 * ap->private_data
 *
@@ -269,8 +324,11 @@ static int sil24_probe_reset(struct ata_port *ap, unsigned int *classes);
static void sil24_qc_prep(struct ata_queued_cmd *qc);
static unsigned int sil24_qc_issue(struct ata_queued_cmd *qc);
static void sil24_irq_clear(struct ata_port *ap);
static void sil24_eng_timeout(struct ata_port *ap);
static irqreturn_t sil24_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
static void sil24_freeze(struct ata_port *ap);
static void sil24_thaw(struct ata_port *ap);
static void sil24_error_handler(struct ata_port *ap);
static void sil24_post_internal_cmd(struct ata_queued_cmd *qc);
static int sil24_port_start(struct ata_port *ap);
static void sil24_port_stop(struct ata_port *ap);
static void sil24_host_stop(struct ata_host_set *host_set);
@@ -325,14 +383,17 @@ static const struct ata_port_operations sil24_ops = {
	.qc_prep		= sil24_qc_prep,
	.qc_issue		= sil24_qc_issue,

	.eng_timeout		= sil24_eng_timeout,

	.irq_handler		= sil24_interrupt,
	.irq_clear		= sil24_irq_clear,

	.scr_read		= sil24_scr_read,
	.scr_write		= sil24_scr_write,

	.freeze			= sil24_freeze,
	.thaw			= sil24_thaw,
	.error_handler		= sil24_error_handler,
	.post_internal_cmd	= sil24_post_internal_cmd,

	.port_start		= sil24_port_start,
	.port_stop		= sil24_port_stop,
	.host_stop		= sil24_host_stop,
@@ -459,7 +520,7 @@ static int sil24_softreset(struct ata_port *ap, unsigned int *class)
	struct sil24_port_priv *pp = ap->private_data;
	struct sil24_prb *prb = &pp->cmd_block[0].ata.prb;
	dma_addr_t paddr = pp->cmd_block_dma;
	u32 mask, irq_enable, irq_stat;
	u32 mask, irq_stat;
	const char *reason;

	DPRINTK("ENTER\n");
@@ -470,10 +531,6 @@ static int sil24_softreset(struct ata_port *ap, unsigned int *class)
		goto out;
	}

	/* temporarily turn off IRQs during SRST */
	irq_enable = readl(port + PORT_IRQ_ENABLE_SET);
	writel(irq_enable, port + PORT_IRQ_ENABLE_CLR);

	/* put the port into known state */
	if (sil24_init_port(ap)) {
		reason ="port not ready";
@@ -494,9 +551,6 @@ static int sil24_softreset(struct ata_port *ap, unsigned int *class)
	writel(irq_stat, port + PORT_IRQ_STAT); /* clear IRQs */
	irq_stat >>= PORT_IRQ_RAW_SHIFT;

	/* restore IRQs */
	writel(irq_enable, port + PORT_IRQ_ENABLE_SET);

	if (!(irq_stat & PORT_IRQ_COMPLETE)) {
		if (irq_stat & PORT_IRQ_ERROR)
			reason = "SRST command error";
@@ -655,158 +709,134 @@ static void sil24_irq_clear(struct ata_port *ap)
	/* unused */
}

static int __sil24_restart_controller(void __iomem *port)
static void sil24_freeze(struct ata_port *ap)
{
	u32 tmp;
	int cnt;

	writel(PORT_CS_INIT, port + PORT_CTRL_STAT);

	/* Max ~10ms */
	for (cnt = 0; cnt < 10000; cnt++) {
		tmp = readl(port + PORT_CTRL_STAT);
		if (tmp & PORT_CS_RDY)
			return 0;
		udelay(1);
	}

	return -1;
}
	void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;

static void sil24_restart_controller(struct ata_port *ap)
{
	if (__sil24_restart_controller((void __iomem *)ap->ioaddr.cmd_addr))
		printk(KERN_ERR DRV_NAME
		       " ata%u: failed to restart controller\n", ap->id);
	/* Port-wide IRQ mask in HOST_CTRL doesn't really work, clear
	 * PORT_IRQ_ENABLE instead.
	 */
	writel(0xffff, port + PORT_IRQ_ENABLE_CLR);
}

static int __sil24_reset_controller(void __iomem *port)
static void sil24_thaw(struct ata_port *ap)
{
	int cnt;
	void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
	u32 tmp;

	/* Reset controller state.  Is this correct? */
	writel(PORT_CS_DEV_RST, port + PORT_CTRL_STAT);
	readl(port + PORT_CTRL_STAT);	/* sync */
	/* clear IRQ */
	tmp = readl(port + PORT_IRQ_STAT);
	writel(tmp, port + PORT_IRQ_STAT);

	/* Max ~100ms */
	for (cnt = 0; cnt < 1000; cnt++) {
		udelay(100);
		tmp = readl(port + PORT_CTRL_STAT);
		if (!(tmp & PORT_CS_DEV_RST))
			break;
	/* turn IRQ back on */
	writel(DEF_PORT_IRQ, port + PORT_IRQ_ENABLE_SET);
}

	if (tmp & PORT_CS_DEV_RST)
		return -1;

	if (tmp & PORT_CS_RDY)
		return 0;

	return __sil24_restart_controller(port);
}

static void sil24_reset_controller(struct ata_port *ap)
static void sil24_error_intr(struct ata_port *ap)
{
	printk(KERN_NOTICE DRV_NAME
	       " ata%u: resetting controller...\n", ap->id);
	if (__sil24_reset_controller((void __iomem *)ap->ioaddr.cmd_addr))
                printk(KERN_ERR DRV_NAME
                       " ata%u: failed to reset controller\n", ap->id);
}
	void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
	struct ata_eh_info *ehi = &ap->eh_info;
	int freeze = 0;
	u32 irq_stat;

static void sil24_eng_timeout(struct ata_port *ap)
{
	struct ata_queued_cmd *qc;
	/* on error, we need to clear IRQ explicitly */
	irq_stat = readl(port + PORT_IRQ_STAT);
	writel(irq_stat, port + PORT_IRQ_STAT);

	qc = ata_qc_from_tag(ap, ap->active_tag);
	/* first, analyze and record host port events */
	ata_ehi_clear_desc(ehi);

	ata_port_printk(ap, KERN_ERR, "command timeout\n");
	qc->err_mask |= AC_ERR_TIMEOUT;
	ata_eh_qc_complete(qc);
	ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat);

	sil24_reset_controller(ap);
	if (irq_stat & PORT_IRQ_DEV_XCHG) {
		ehi->err_mask |= AC_ERR_ATA_BUS;
		/* sil24 doesn't recover very well from phy
		 * disconnection with a softreset.  Force hardreset.
		 */
		ehi->action |= ATA_EH_HARDRESET;
		ata_ehi_push_desc(ehi, ", device_exchanged");
		freeze = 1;
	}

static void sil24_error_intr(struct ata_port *ap, u32 slot_stat)
{
	struct ata_queued_cmd *qc = ata_qc_from_tag(ap, ap->active_tag);
	struct sil24_port_priv *pp = ap->private_data;
	void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
	u32 irq_stat, cmd_err, sstatus, serror;
	unsigned int err_mask;

	irq_stat = readl(port + PORT_IRQ_STAT);
	writel(irq_stat, port + PORT_IRQ_STAT);		/* clear irq */

	if (!(irq_stat & PORT_IRQ_ERROR)) {
		/* ignore non-completion, non-error irqs for now */
		printk(KERN_WARNING DRV_NAME
		       "ata%u: non-error exception irq (irq_stat %x)\n",
		       ap->id, irq_stat);
		return;
	if (irq_stat & PORT_IRQ_UNK_FIS) {
		ehi->err_mask |= AC_ERR_HSM;
		ehi->action |= ATA_EH_SOFTRESET;
		ata_ehi_push_desc(ehi , ", unknown FIS");
		freeze = 1;
	}

	cmd_err = readl(port + PORT_CMD_ERR);
	sstatus = readl(port + PORT_SSTATUS);
	serror = readl(port + PORT_SERROR);
	if (serror)
		writel(serror, port + PORT_SERROR);
	/* deal with command error */
	if (irq_stat & PORT_IRQ_ERROR) {
		struct sil24_cerr_info *ci = NULL;
		unsigned int err_mask = 0, action = 0;
		struct ata_queued_cmd *qc;
		u32 cerr;

	/*
	 * Don't log ATAPI device errors.  They're supposed to happen
	 * and any serious errors will be logged using sense data by
	 * the SCSI layer.
	 */
	if (ap->device[0].class != ATA_DEV_ATAPI || cmd_err > PORT_CERR_SDB)
		printk("ata%u: error interrupt on port%d\n"
		       "  stat=0x%x irq=0x%x cmd_err=%d sstatus=0x%x serror=0x%x\n",
		       ap->id, ap->port_no, slot_stat, irq_stat, cmd_err, sstatus, serror);
		/* analyze CMD_ERR */
		cerr = readl(port + PORT_CMD_ERR);
		if (cerr < ARRAY_SIZE(sil24_cerr_db))
			ci = &sil24_cerr_db[cerr];

	if (cmd_err == PORT_CERR_DEV || cmd_err == PORT_CERR_SDB) {
		/*
		 * Device is reporting error, tf registers are valid.
		 */
		sil24_update_tf(ap);
		err_mask = ac_err_mask(pp->tf.command);
		sil24_restart_controller(ap);
		if (ci && ci->desc) {
			err_mask |= ci->err_mask;
			action |= ci->action;
			ata_ehi_push_desc(ehi, ", %s", ci->desc);
		} else {
		/*
		 * Other errors.  libata currently doesn't have any
		 * mechanism to report these errors.  Just turn on
		 * ATA_ERR.
		 */
		err_mask = AC_ERR_OTHER;
		sil24_reset_controller(ap);
			err_mask |= AC_ERR_OTHER;
			action |= ATA_EH_SOFTRESET;
			ata_ehi_push_desc(ehi, ", unknown command error %d",
					  cerr);
		}

		/* record error info */
		qc = ata_qc_from_tag(ap, ap->active_tag);
		if (qc) {
			int tag = qc->tag;
			if (unlikely(ata_tag_internal(tag)))
				tag = 0;
			sil24_update_tf(ap);
			qc->err_mask |= err_mask;
		ata_qc_complete(qc);
		} else
			ehi->err_mask |= err_mask;

		ehi->action |= action;
	}

	/* freeze or abort */
	if (freeze)
		ata_port_freeze(ap);
	else
		ata_port_abort(ap);
}

static inline void sil24_host_intr(struct ata_port *ap)
{
	struct ata_queued_cmd *qc = ata_qc_from_tag(ap, ap->active_tag);
	void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
	struct ata_queued_cmd *qc;
	u32 slot_stat;

	slot_stat = readl(port + PORT_SLOT_STAT);
	if (!(slot_stat & HOST_SSTAT_ATTN)) {
		struct sil24_port_priv *pp = ap->private_data;

	if (unlikely(slot_stat & HOST_SSTAT_ATTN)) {
		sil24_error_intr(ap);
		return;
	}

	if (ap->flags & SIL24_FLAG_PCIX_IRQ_WOC)
		writel(PORT_IRQ_COMPLETE, port + PORT_IRQ_STAT);

	qc = ata_qc_from_tag(ap, ap->active_tag);
	if (qc) {
		if (qc->flags & ATA_QCFLAG_RESULT_TF)
			sil24_update_tf(ap);
			qc->err_mask |= ac_err_mask(pp->tf.command);
		ata_qc_complete(qc);
		return;
	}
	} else
		sil24_error_intr(ap, slot_stat);

	if (ata_ratelimit())
		ata_port_printk(ap, KERN_INFO, "spurious interrupt "
			"(slot_stat 0x%x active_tag %d)\n",
			slot_stat, ap->active_tag);
}

static irqreturn_t sil24_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
@@ -846,6 +876,31 @@ static irqreturn_t sil24_interrupt(int irq, void *dev_instance, struct pt_regs *
	return IRQ_RETVAL(handled);
}

static void sil24_error_handler(struct ata_port *ap)
{
	struct ata_eh_context *ehc = &ap->eh_context;

	if (sil24_init_port(ap)) {
		ata_eh_freeze_port(ap);
		ehc->i.action |= ATA_EH_HARDRESET;
	}

	/* perform recovery */
	ata_do_eh(ap, sil24_softreset, sil24_hardreset, ata_std_postreset);
}

static void sil24_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;

	/* make DMA engine forget about the failed command */
	if (qc->err_mask)
		sil24_init_port(ap);
}

static inline void sil24_cblk_free(struct sil24_port_priv *pp, struct device *dev)
{
	const size_t cb_size = sizeof(*pp->cmd_block);
@@ -1058,15 +1113,6 @@ static int sil24_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
		/* Always use 64bit activation */
		writel(PORT_CS_32BIT_ACTV, port + PORT_CTRL_CLR);

		/* Configure interrupts */
		writel(0xffff, port + PORT_IRQ_ENABLE_CLR);
		writel(PORT_IRQ_COMPLETE | PORT_IRQ_ERROR |
		       PORT_IRQ_SDB_NOTIFY, port + PORT_IRQ_ENABLE_SET);

		/* Clear interrupts */
		writel(0x0fff0fff, port + PORT_IRQ_STAT);
		writel(PORT_CS_IRQ_WOC, port + PORT_CTRL_CLR);

		/* Clear port multiplier enable and resume bits */
		writel(PORT_CS_PM_EN | PORT_CS_RESUME, port + PORT_CTRL_CLR);
	}