Module Name:    src
Committed By:   jdolecek
Date:           Thu Oct 11 20:57:51 UTC 2018

Modified Files:
        src/sys/dev/ata [jdolecek-ncqfixes]: TODO.ncq ata.c ata_subr.c atavar.h
            files.ata
        src/sys/dev/ic [jdolecek-ncqfixes]: ahcisata_core.c ahcisatavar.h
            mvsata.c mvsatavar.h siisata.c siisatavar.h
Added Files:
        src/sys/dev/ata [jdolecek-ncqfixes]: ata_recovery.c

Log Message:
refactor shared parts of the SATA error recovery into new function
ata_recovery_resume() and use for ahcisata/siisata/mvsata, also replace
per-controller hold/unhold with generic version

move the shared recovery code into separate file ata_recovery.c


To generate a diff of this commit:
cvs rdiff -u -r1.4.2.12 -r1.4.2.13 src/sys/dev/ata/TODO.ncq
cvs rdiff -u -r1.141.6.15 -r1.141.6.16 src/sys/dev/ata/ata.c
cvs rdiff -u -r0 -r1.1.2.1 src/sys/dev/ata/ata_recovery.c
cvs rdiff -u -r1.6.2.7 -r1.6.2.8 src/sys/dev/ata/ata_subr.c
cvs rdiff -u -r1.99.2.10 -r1.99.2.11 src/sys/dev/ata/atavar.h
cvs rdiff -u -r1.27 -r1.27.6.1 src/sys/dev/ata/files.ata
cvs rdiff -u -r1.62.2.8 -r1.62.2.9 src/sys/dev/ic/ahcisata_core.c
cvs rdiff -u -r1.18 -r1.18.6.1 src/sys/dev/ic/ahcisatavar.h
cvs rdiff -u -r1.41.2.6 -r1.41.2.7 src/sys/dev/ic/mvsata.c
cvs rdiff -u -r1.4 -r1.4.2.1 src/sys/dev/ic/mvsatavar.h
cvs rdiff -u -r1.35.6.8 -r1.35.6.9 src/sys/dev/ic/siisata.c
cvs rdiff -u -r1.7 -r1.7.6.1 src/sys/dev/ic/siisatavar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/ata/TODO.ncq
diff -u src/sys/dev/ata/TODO.ncq:1.4.2.12 src/sys/dev/ata/TODO.ncq:1.4.2.13
--- src/sys/dev/ata/TODO.ncq:1.4.2.12	Sat Oct  6 20:27:28 2018
+++ src/sys/dev/ata/TODO.ncq	Thu Oct 11 20:57:51 2018
@@ -4,7 +4,8 @@ jdolecek-ncqfixes goals:
 - run recovery via atathread, move to new function and share ahci/siisata/mvsata
 - maybe do device error handling in not-interrupt-context (maybe this should be
   done on a mpata branch?)
-- remove controller-specific slot bitmaps (ic/siisata.c, ic/ahcisata_core.c)
+- adjust mvsata() intr code to accept tfd (instead of irq 0/1) so that
+  ata_recovery_resume() works properly for it
 
 Bugs
 ----

Index: src/sys/dev/ata/ata.c
diff -u src/sys/dev/ata/ata.c:1.141.6.15 src/sys/dev/ata/ata.c:1.141.6.16
--- src/sys/dev/ata/ata.c:1.141.6.15	Sat Oct  6 21:19:55 2018
+++ src/sys/dev/ata/ata.c	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: ata.c,v 1.141.6.15 2018/10/06 21:19:55 jdolecek Exp $	*/
+/*	$NetBSD: ata.c,v 1.141.6.16 2018/10/11 20:57:51 jdolecek Exp $	*/
 
 /*
  * Copyright (c) 1998, 2001 Manuel Bouyer.  All rights reserved.
@@ -25,7 +25,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ata.c,v 1.141.6.15 2018/10/06 21:19:55 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ata.c,v 1.141.6.16 2018/10/11 20:57:51 jdolecek Exp $");
 
 #include "opt_ata.h"
 
@@ -933,98 +933,6 @@ out:
 	return rv;
 }
 
-int
-ata_read_log_ext_ncq(struct ata_drive_datas *drvp, uint8_t flags,
-    uint8_t *slot, uint8_t *status, uint8_t *err)
-{
-	struct ata_xfer *xfer = &drvp->recovery_xfer;
-	int rv;
-	struct ata_channel *chp = drvp->chnl_softc;
-	struct atac_softc *atac = chp->ch_atac;
-	uint8_t *tb, cksum, page;
-
-	ATADEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);
-
-	/* Only NCQ ATA drives support/need this */
-	if (drvp->drive_type != ATA_DRIVET_ATA ||
-	    (drvp->drive_flags & ATA_DRIVE_NCQ) == 0)
-		return EOPNOTSUPP;
-
-	memset(xfer, 0, sizeof(*xfer));
-
-	tb = drvp->recovery_blk;
-	memset(tb, 0, sizeof(drvp->recovery_blk));
-
-	/*
-	 * We could use READ LOG DMA EXT if drive supports it (i.e.
-	 * when it supports Streaming feature) to avoid PIO command,
-	 * and to make this a little faster. Realistically, it
-	 * should not matter.
-	 */
-	xfer->c_flags |= C_SKIP_QUEUE;
-	xfer->c_ata_c.r_command = WDCC_READ_LOG_EXT;
-	xfer->c_ata_c.r_lba = page = WDCC_LOG_PAGE_NCQ;
-	xfer->c_ata_c.r_st_bmask = WDCS_DRDY;
-	xfer->c_ata_c.r_st_pmask = WDCS_DRDY;
-	xfer->c_ata_c.r_count = 1;
-	xfer->c_ata_c.r_device = WDSD_LBA;
-	xfer->c_ata_c.flags = AT_READ | AT_LBA | AT_LBA48 | flags;
-	xfer->c_ata_c.timeout = 1000; /* 1s */
-	xfer->c_ata_c.data = tb;
-	xfer->c_ata_c.bcount = sizeof(drvp->recovery_blk);
-
-	if ((*atac->atac_bustype_ata->ata_exec_command)(drvp,
-						xfer) != ATACMD_COMPLETE) {
-		rv = EAGAIN;
-		goto out;
-	}
-	if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) {
-		rv = EINVAL;
-		goto out;
-	}
-
-	cksum = 0;
-	for (int i = 0; i < sizeof(drvp->recovery_blk); i++)
-		cksum += tb[i];
-	if (cksum != 0) {
-		device_printf(drvp->drv_softc,
-		    "invalid checksum %x for READ LOG EXT page %x\n",
-		    cksum, page);
-		rv = EINVAL;
-		goto out;
-	}
-
-	if (tb[0] & WDCC_LOG_NQ) {
-		/* not queued command */
-		rv = EOPNOTSUPP;
-		goto out;
-	}
-
-	*slot = tb[0] & 0x1f;
-	*status = tb[2];
-	*err = tb[3];
-
-	if ((*status & WDCS_ERR) == 0) {
-		/*
-		 * We expect error here. Normal physical drives always
-		 * do, it's part of ATA standard. However, QEMU AHCI emulation
-		 * misehandles READ LOG EXT in a way that the command itself
-		 * returns without error, but no data is transferred.
-		 */
-		device_printf(drvp->drv_softc,
-		    "READ LOG EXT page %x failed to report error: "
-		    "slot %d err %x status %x\n",
-		    page, *slot, *err, *status);
-		rv = EOPNOTSUPP;
-		goto out;
-	}
-
-	rv = 0;
-
-out:
-	return rv;
-}
-
 #if NATA_DMA
 void
 ata_dmaerr(struct ata_drive_datas *drvp, int flags)

Index: src/sys/dev/ata/ata_subr.c
diff -u src/sys/dev/ata/ata_subr.c:1.6.2.7 src/sys/dev/ata/ata_subr.c:1.6.2.8
--- src/sys/dev/ata/ata_subr.c:1.6.2.7	Sat Oct  6 21:19:55 2018
+++ src/sys/dev/ata/ata_subr.c	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: ata_subr.c,v 1.6.2.7 2018/10/06 21:19:55 jdolecek Exp $	*/
+/*	$NetBSD: ata_subr.c,v 1.6.2.8 2018/10/11 20:57:51 jdolecek Exp $	*/
 
 /*
  * Copyright (c) 1998, 2001 Manuel Bouyer.  All rights reserved.
@@ -25,7 +25,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ata_subr.c,v 1.6.2.7 2018/10/06 21:19:55 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ata_subr.c,v 1.6.2.8 2018/10/11 20:57:51 jdolecek Exp $");
 
 #include "opt_ata.h"
 
@@ -333,3 +333,42 @@ ata_queue_free_slot(struct ata_channel *
 
 	chq->queue_xfers_avail |= __BIT(c_slot);
 }
+
+void
+ata_queue_hold(struct ata_channel *chp)
+{
+	struct ata_queue *chq = chp->ch_queue;
+
+	KASSERT(mutex_owned(&chp->ch_lock));
+	
+	chq->queue_hold |= chq->active_xfers_used;
+	chq->active_xfers_used = 0;
+}
+
+void
+ata_queue_unhold(struct ata_channel *chp)
+{
+	struct ata_queue *chq = chp->ch_queue;
+
+	KASSERT(mutex_owned(&chp->ch_lock));
+
+	chq->active_xfers_used |= chq->queue_hold;
+	chq->queue_hold = 0;
+}
+
+/*
+ * Must be called with interrupts blocked.
+ */
+uint32_t
+ata_queue_active(struct ata_channel *chp)
+{
+	struct ata_queue *chq = chp->ch_queue;
+
+	return chq->active_xfers_used;
+}
+
+uint8_t
+ata_queue_openings(struct ata_channel *chp)
+{
+	return chp->ch_queue->queue_openings;
+}

Index: src/sys/dev/ata/atavar.h
diff -u src/sys/dev/ata/atavar.h:1.99.2.10 src/sys/dev/ata/atavar.h:1.99.2.11
--- src/sys/dev/ata/atavar.h:1.99.2.10	Sat Oct  6 21:19:55 2018
+++ src/sys/dev/ata/atavar.h	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: atavar.h,v 1.99.2.10 2018/10/06 21:19:55 jdolecek Exp $	*/
+/*	$NetBSD: atavar.h,v 1.99.2.11 2018/10/11 20:57:51 jdolecek Exp $	*/
 
 /*
  * Copyright (c) 1998, 2001 Manuel Bouyer.
@@ -233,6 +233,7 @@ struct ata_queue {
 	TAILQ_HEAD(, ata_xfer) active_xfers; 	/* active commands */
 	uint32_t active_xfers_used;		/* mask of active commands */
 	uint32_t queue_xfers_avail;		/* available xfers mask */
+	uint32_t queue_hold;			/* slots held during recovery */
 	kcondvar_t c_active;		/* somebody actively waiting for xfer */
 	kcondvar_t c_cmd_finish;	/* somebody waiting for cmd finish */
 };
@@ -414,6 +415,7 @@ struct ata_channel {
 #define ATACH_NCQ	0x800	/* channel executing NCQ commands */
 #define ATACH_DMA_BEFORE_CMD	0x1000	/* start DMA first */
 #define ATACH_TH_DRIVE_RESET	0x2000	/* thread asked for drive(s) reset */
+#define ATACH_RECOVERING	0x4000	/* channel is recovering */
 
 #define ATACH_NODRIVE	0xff	/* no drive selected for reset */
 
@@ -524,6 +526,7 @@ int	ata_get_params(struct ata_drive_data
 int	ata_set_mode(struct ata_drive_datas *, uint8_t, uint8_t);
 int	ata_read_log_ext_ncq(struct ata_drive_datas *, uint8_t, uint8_t *,
     uint8_t *, uint8_t *);
+void	ata_recovery_resume(struct ata_channel *, int, int, int);
 
 /* return code for these cmds */
 #define CMD_OK    0
@@ -573,6 +576,10 @@ struct ata_xfer *
 	ata_queue_drive_active_xfer(struct ata_channel *, int);
 bool	ata_queue_alloc_slot(struct ata_channel *, uint8_t *, uint8_t);
 void	ata_queue_free_slot(struct ata_channel *, uint8_t);
+uint32_t	ata_queue_active(struct ata_channel *);
+uint8_t	ata_queue_openings(struct ata_channel *);
+void	ata_queue_hold(struct ata_channel *);
+void	ata_queue_unhold(struct ata_channel *);
 
 void	ata_delay(struct ata_channel *, int, const char *, int);
 

Index: src/sys/dev/ata/files.ata
diff -u src/sys/dev/ata/files.ata:1.27 src/sys/dev/ata/files.ata:1.27.6.1
--- src/sys/dev/ata/files.ata:1.27	Tue Oct 10 17:19:38 2017
+++ src/sys/dev/ata/files.ata	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-#	$NetBSD: files.ata,v 1.27 2017/10/10 17:19:38 jdolecek Exp $
+#	$NetBSD: files.ata,v 1.27.6.1 2018/10/11 20:57:51 jdolecek Exp $
 #
 # Config file and device description for machine-independent devices
 # which attach to ATA busses.  Included by ports that need it.  Ports
@@ -16,6 +16,7 @@ defflag	opt_wd.h	WD_CHAOS_MONKEY
 
 file	dev/ata/ata.c			(ata_hl | atapi) & atabus
 file	dev/ata/ata_subr.c		(ata_hl | atapi)
+file	dev/ata/ata_recovery.c		(ata_hl | atapi)
 
 # ATA RAID configuration support
 defpseudodev ataraid {[vendtype = -1], [unit = -1]}

Index: src/sys/dev/ic/ahcisata_core.c
diff -u src/sys/dev/ic/ahcisata_core.c:1.62.2.8 src/sys/dev/ic/ahcisata_core.c:1.62.2.9
--- src/sys/dev/ic/ahcisata_core.c:1.62.2.8	Sun Oct  7 15:44:47 2018
+++ src/sys/dev/ic/ahcisata_core.c	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: ahcisata_core.c,v 1.62.2.8 2018/10/07 15:44:47 jdolecek Exp $	*/
+/*	$NetBSD: ahcisata_core.c,v 1.62.2.9 2018/10/11 20:57:51 jdolecek Exp $	*/
 
 /*
  * Copyright (c) 2006 Manuel Bouyer.
@@ -26,7 +26,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ahcisata_core.c,v 1.62.2.8 2018/10/07 15:44:47 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ahcisata_core.c,v 1.62.2.9 2018/10/11 20:57:51 jdolecek Exp $");
 
 #include <sys/types.h>
 #include <sys/malloc.h>
@@ -569,6 +569,7 @@ ahci_intr_port(struct ahci_softc *sc, st
 	struct ata_xfer *xfer;
 	int slot = -1;
 	bool recover = false;
+	uint32_t aslots;
 
 	is = AHCI_READ(sc, AHCI_P_IS(chp->ch_channel));
 	AHCI_WRITE(sc, AHCI_P_IS(chp->ch_channel), is);
@@ -622,14 +623,14 @@ ahci_intr_port(struct ahci_softc *sc, st
 			    DEBUG_INTR);
 		}
 
-		if (!achp->ahcic_recovering)
+		if (!ISSET(chp->ch_flags, ATACH_RECOVERING))
 			recover = true;
 	} else if (is & (AHCI_P_IX_DHRS|AHCI_P_IX_SDBS)) {
 		tfd = AHCI_READ(sc, AHCI_P_TFD(chp->ch_channel));
 
 		/* D2H Register FIS or Set Device Bits */
 		if ((tfd & WDCS_ERR) != 0) {
-			if (!achp->ahcic_recovering)
+			if (!ISSET(chp->ch_flags, ATACH_RECOVERING))
 				recover = true;
 
 			AHCIDEBUG_PRINT(("%s port %d: transfer aborted 0x%x\n",
@@ -643,8 +644,10 @@ ahci_intr_port(struct ahci_softc *sc, st
 	if (__predict_false(recover))
 		ata_channel_freeze(chp);
 
+	aslots = ata_queue_active(chp);
+
 	if (slot >= 0) {
-		if ((achp->ahcic_cmds_active & __BIT(slot)) != 0 &&
+		if ((aslots & __BIT(slot)) != 0 &&
 		    (sact & __BIT(slot)) == 0) {
 			xfer = ata_queue_hwslot_to_xfer(chp, slot);
 			xfer->ops->c_intr(chp, xfer, tfd);
@@ -659,7 +662,6 @@ ahci_intr_port(struct ahci_softc *sc, st
 		 * can activate another command(s), so must only process
 		 * commands active before we start processing.
 		 */
-		uint32_t aslots = achp->ahcic_cmds_active;
 
 		for (slot=0; slot < sc->sc_ncmds; slot++) {
 			if ((aslots & __BIT(slot)) != 0 &&
@@ -1071,7 +1073,6 @@ ahci_cmd_start(struct ata_channel *chp, 
 	    DEBUG_XFERS);
 
 	ata_channel_lock_owned(chp);
-	KASSERT((achp->ahcic_cmds_active & (1U << slot)) == 0);
 
 	cmd_tbl = achp->ahcic_cmd_tbl[slot];
 	AHCIDEBUG_PRINT(("%s port %d tbl %p\n", AHCINAME(sc), chp->ch_channel,
@@ -1105,8 +1106,6 @@ ahci_cmd_start(struct ata_channel *chp, 
 	}
 	/* start command */
 	AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << slot);
-	/* and says we started this command */
-	achp->ahcic_cmds_active |= 1U << slot;
 
 	if ((ata_c->flags & AT_POLL) == 0) {
 		callout_reset(&chp->c_timo_callout, mstohz(ata_c->timeout),
@@ -1164,7 +1163,6 @@ ahci_cmd_abort(struct ata_channel *chp, 
 static void
 ahci_cmd_kill_xfer(struct ata_channel *chp, struct ata_xfer *xfer, int reason)
 {
-	struct ahci_channel *achp = (struct ahci_channel *)chp;
 	struct ata_command *ata_c = &xfer->c_ata_c;
 	bool deactivate = true;
 
@@ -1191,11 +1189,8 @@ ahci_cmd_kill_xfer(struct ata_channel *c
 
 	ahci_cmd_done_end(chp, xfer);
 
-	if (deactivate) {
-		KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-		achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 }
 
 static int
@@ -1229,8 +1224,6 @@ ahci_cmd_complete(struct ata_channel *ch
 
 	ahci_cmd_done(chp, xfer);
 
-	KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-	achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
 	ata_deactivate_xfer(chp, xfer);
 
 	if ((ata_c->flags & (AT_TIMEOU|AT_ERROR)) == 0)
@@ -1359,8 +1352,6 @@ ahci_bio_start(struct ata_channel *chp, 
 		AHCI_WRITE(sc, AHCI_P_SACT(chp->ch_channel), 1U << xfer->c_slot);
 	/* start command */
 	AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << xfer->c_slot);
-	/* and says we started this command */
-	achp->ahcic_cmds_active |= 1U << xfer->c_slot;
 
 	if ((xfer->c_flags & C_POLL) == 0) {
 		callout_reset(&chp->c_timo_callout, mstohz(ATA_DELAY),
@@ -1413,7 +1404,6 @@ ahci_bio_kill_xfer(struct ata_channel *c
 {
 	int drive = xfer->c_drive;
 	struct ata_bio *ata_bio = &xfer->c_bio;
-	struct ahci_channel *achp = (struct ahci_channel *)chp;
 	bool deactivate = true;
 
 	AHCIDEBUG_PRINT(("ahci_bio_kill_xfer channel %d\n", chp->ch_channel),
@@ -1439,11 +1429,8 @@ ahci_bio_kill_xfer(struct ata_channel *c
 	}
 	ata_bio->r_error = WDCE_ABRT;
 
-	if (deactivate) {
-		KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-		achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 
 	(*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer);
 }
@@ -1503,8 +1490,6 @@ ahci_bio_complete(struct ata_channel *ch
 	}
 	AHCIDEBUG_PRINT((" now %ld\n", ata_bio->bcount), DEBUG_XFERS);
 
-	KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-	achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
 	ata_deactivate_xfer(chp, xfer);
 
 	(*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer);
@@ -1579,34 +1564,17 @@ ahci_channel_start(struct ahci_softc *sc
 	AHCI_WRITE(sc, AHCI_P_CMD(chp->ch_channel), p_cmd);
 }
 
-static void
-ahci_hold(struct ahci_channel *achp)
-{
-	achp->ahcic_cmds_hold |= achp->ahcic_cmds_active;
-	achp->ahcic_cmds_active = 0;
-}
-
-static void
-ahci_unhold(struct ahci_channel *achp)
-{
-	achp->ahcic_cmds_active = achp->ahcic_cmds_hold;
-	achp->ahcic_cmds_hold = 0;
-}
-
 /* Recover channel after command failure */
 void
 ahci_channel_recover(struct ahci_softc *sc, struct ata_channel *chp, int tfd)
 {
-	struct ahci_channel *achp = (struct ahci_channel *)chp;
-	struct ata_drive_datas *drvp;
-	uint8_t slot, eslot, st, err;
-	int drive = -1, error;
-	struct ata_xfer *xfer;
+	int drive = ATACH_NODRIVE;
 	bool reset = false;
 
-	KASSERT(!achp->ahcic_recovering);
-
-	achp->ahcic_recovering = true;
+	ata_channel_lock(chp);
+	KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING));
+	SET(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
 
 	/*
 	 * Read FBS to get the drive which caused the error, if PM is in use.
@@ -1637,19 +1605,17 @@ ahci_channel_recover(struct ahci_softc *
 			}
 			if ((fbs & AHCI_P_FBS_DEC) != 0) {
 				/* follow non-device specific recovery */
-				drive = -1;
+				drive = ATACH_NODRIVE;
 				reset = true;
 			}
 		} else {
 			/* not device specific, reset channel */
-			drive = -1;
+			drive = ATACH_NODRIVE;
 			reset = true;
 		}
 	} else
 		drive = 0;
 
-	drvp = &chp->ch_drive[drive];
-
 	/*
 	 * If BSY or DRQ bits are set, must execute COMRESET to return
 	 * device to idle state. If drive is idle, it's enough to just
@@ -1657,86 +1623,26 @@ ahci_channel_recover(struct ahci_softc *
 	 * After resetting CMD.ST, need to execute READ LOG EXT for NCQ
 	 * to unblock device processing if COMRESET was not done.
 	 */
-	if (reset || (AHCI_TFD_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0)
-		goto reset;
-
-	KASSERT(drive >= 0);
-	ahci_channel_stop(sc, chp, AT_POLL);
-	ahci_channel_start(sc, chp, AT_POLL,
-   	    (sc->sc_ahci_cap & AHCI_CAP_CLO) ? 1 : 0);
-
-	ahci_hold(achp);
-
-	/*
-	 * When running NCQ commands, READ LOG EXT is necessary to clear the
-	 * error condition and unblock the device.
-	 */
-	error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err);
-
-	ahci_unhold(achp);
-
-	switch (error) {
-	case 0:
-		/* Error out the particular NCQ xfer, then requeue the others */
-		if ((achp->ahcic_cmds_active & (1U << eslot)) != 0) {
-			xfer = ata_queue_hwslot_to_xfer(chp, eslot);
-			xfer->c_flags |= C_RECOVERED;
-			xfer->ops->c_intr(chp, xfer,
-			    (err << AHCI_P_TFD_ERR_SHIFT) | st);
-		}
-		break;
-
-	case EOPNOTSUPP:
-		/*
-		 * Non-NCQ command error, just find the slot and end with
-		 * the error.
-		 */
-		for (slot = 0; slot < sc->sc_ncmds; slot++) {
-			if ((achp->ahcic_cmds_active & (1U << slot)) != 0) {
-				xfer = ata_queue_hwslot_to_xfer(chp, slot);
-				xfer->ops->c_intr(chp, xfer, tfd);
-			}
-		}
-		break;
-
-	case EAGAIN:
-		/*
-		 * Failed to get resources to run the recovery command, must
-		 * reset the drive. This will also kill all still outstanding
-		 * transfers.
-		 */
-reset:
+	if (reset || (AHCI_TFD_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) {
 		ata_channel_lock(chp);
 		ahci_reset_channel(chp, AT_POLL);
 		ata_channel_unlock(chp);
 		goto out;
-		/* NOTREACHED */
-
-	default:
-		/*
-		 * The command to get the slot failed. Kill outstanding
-		 * commands for the same drive only. No need to reset
-		 * the drive, it's unblocked nevertheless.
-		 */
-		break;
 	}
 
-	/* Requeue all unfinished commands for same drive as failed command */ 
-	for (slot = 0; slot < sc->sc_ncmds; slot++) {
-		if ((achp->ahcic_cmds_active & (1U << slot)) == 0)
-			continue;
-
-		xfer = ata_queue_hwslot_to_xfer(chp, slot);
-		if (drive != xfer->c_drive)
-			continue;
+	KASSERT(drive != ATACH_NODRIVE && drive >= 0);
+	ahci_channel_stop(sc, chp, AT_POLL);
+	ahci_channel_start(sc, chp, AT_POLL,
+   	    (sc->sc_ahci_cap & AHCI_CAP_CLO) ? 1 : 0);
 
-		xfer->ops->c_kill_xfer(chp, xfer,
-		    (error == 0) ? KILL_REQUEUE : KILL_RESET);
-	}
+	ata_recovery_resume(chp, drive, tfd, AT_POLL);
 
 out:
 	/* Drive unblocked, back to normal operation */
-	achp->ahcic_recovering = false;
+	ata_channel_lock(chp);
+	CLR(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
+
 	atastart(chp);
 }
 
@@ -1951,8 +1857,6 @@ ahci_atapi_start(struct ata_channel *chp
 	}
 	/* start command */
 	AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << xfer->c_slot);
-	/* and says we started this command */
-	achp->ahcic_cmds_active |= 1U << xfer->c_slot;
 
 	if ((xfer->c_flags & C_POLL) == 0) {
 		callout_reset(&chp->c_timo_callout, mstohz(sc_xfer->timeout),
@@ -2044,8 +1948,6 @@ ahci_atapi_complete(struct ata_channel *
 		}
 	}
 
-	KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-	achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
 	ata_deactivate_xfer(chp, xfer);
 
 	ata_free_xfer(chp, xfer);
@@ -2059,7 +1961,6 @@ static void
 ahci_atapi_kill_xfer(struct ata_channel *chp, struct ata_xfer *xfer, int reason)
 {
 	struct scsipi_xfer *sc_xfer = xfer->c_scsipi;
-	struct ahci_channel *achp = (struct ahci_channel *)chp;
 	bool deactivate = true;
 
 	/* remove this command from xfer queue */
@@ -2081,11 +1982,8 @@ ahci_atapi_kill_xfer(struct ata_channel 
 		panic("ahci_ata_atapi_kill_xfer");
 	}
 
-	if (deactivate) {
-		KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0);
-		achp->ahcic_cmds_active &= ~(1U << xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 
 	ata_free_xfer(chp, xfer);
 	scsipi_done(sc_xfer);

Index: src/sys/dev/ic/ahcisatavar.h
diff -u src/sys/dev/ic/ahcisatavar.h:1.18 src/sys/dev/ic/ahcisatavar.h:1.18.6.1
--- src/sys/dev/ic/ahcisatavar.h:1.18	Sat Oct  7 16:05:32 2017
+++ src/sys/dev/ic/ahcisatavar.h	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: ahcisatavar.h,v 1.18 2017/10/07 16:05:32 jdolecek Exp $	*/
+/*	$NetBSD: ahcisatavar.h,v 1.18.6.1 2018/10/11 20:57:51 jdolecek Exp $	*/
 
 /*
  * Copyright (c) 2006 Manuel Bouyer.
@@ -82,9 +82,6 @@ struct ahci_softc {
 		struct ahci_cmd_tbl *ahcic_cmd_tbl[AHCI_MAX_CMDS];
 		bus_addr_t ahcic_bus_cmd_tbl[AHCI_MAX_CMDS];
 		bus_dmamap_t ahcic_datad[AHCI_MAX_CMDS];
-		volatile uint32_t  ahcic_cmds_active;	/* active commands */
-		uint32_t  ahcic_cmds_hold; 	/* held commands */
-		bool ahcic_recovering;
 	} sc_channels[AHCI_MAX_PORTS];
 
 	void	(*sc_channel_start)(struct ahci_softc *, struct ata_channel *);

Index: src/sys/dev/ic/mvsata.c
diff -u src/sys/dev/ic/mvsata.c:1.41.2.6 src/sys/dev/ic/mvsata.c:1.41.2.7
--- src/sys/dev/ic/mvsata.c:1.41.2.6	Thu Oct  4 17:59:35 2018
+++ src/sys/dev/ic/mvsata.c	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: mvsata.c,v 1.41.2.6 2018/10/04 17:59:35 jdolecek Exp $	*/
+/*	$NetBSD: mvsata.c,v 1.41.2.7 2018/10/11 20:57:51 jdolecek Exp $	*/
 /*
  * Copyright (c) 2008 KIYOHARA Takashi
  * All rights reserved.
@@ -26,7 +26,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: mvsata.c,v 1.41.2.6 2018/10/04 17:59:35 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: mvsata.c,v 1.41.2.7 2018/10/11 20:57:51 jdolecek Exp $");
 
 #include "opt_mvsata.h"
 
@@ -172,8 +172,6 @@ static void mvsata_channel_recover(struc
 static int mvsata_port_init(struct mvsata_hc *, int);
 static int mvsata_wdc_reg_init(struct mvsata_port *, struct wdc_regs *);
 #ifndef MVSATA_WITHOUTDMA
-static inline void mvsata_quetag_get(struct mvsata_port *, uint8_t);
-static inline void mvsata_quetag_put(struct mvsata_port *, uint8_t);
 static void *mvsata_edma_resource_prepare(struct mvsata_port *, bus_dma_tag_t,
 					  bus_dmamap_t *, size_t, int);
 static void mvsata_edma_resource_purge(struct mvsata_port *, bus_dma_tag_t,
@@ -539,7 +537,8 @@ mvsata_error(struct mvsata_port *mvport)
 		    device_xname(MVSATA_DEV2(mvport)),
 		    mvport->port_hc->hc, mvport->port);
 
-		if (!mvport->port_recovering)
+		if (!ISSET(mvport->port_ata_channel.ch_flags,
+		    ATACH_RECOVERING))
 			mvsata_channel_recover(mvport);
 	}
 
@@ -547,31 +546,15 @@ mvsata_error(struct mvsata_port *mvport)
 }
 
 static void
-mvsata_hold(struct mvsata_port *mvport)
-{
-	mvport->port_hold_slots |= mvport->port_quetagidx;
-	mvport->port_quetagidx = 0;
-}
-
-static void
-mvsata_unhold(struct mvsata_port *mvport)
-{
-	mvport->port_quetagidx = mvport->port_hold_slots;
-	mvport->port_hold_slots = 0;
-}
-
-static void
 mvsata_channel_recover(struct mvsata_port *mvport)
 {
 	struct ata_channel *chp = &mvport->port_ata_channel;
-	struct ata_drive_datas *drvp;
-	int drive, error;
-	uint8_t eslot, slot, st, err;
-	struct ata_xfer *xfer;
+	int drive, tfd = 1;
 
-	KASSERT(!mvport->port_recovering);
-
-	mvport->port_recovering = true;
+	ata_channel_lock(chp);
+	KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING));
+	SET(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
 
 	if (chp->ch_ndrives > PMP_PORT_CTL) {
 		/* Get PM port number for the device in error. This device
@@ -582,90 +565,29 @@ mvsata_channel_recover(struct mvsata_por
 	} else
 		drive = 0;
 
-	drvp = &chp->ch_drive[drive];
-
 	/*
 	 * Controller doesn't need any special action. Simply execute
 	 * READ LOG EXT for NCQ to unblock device processing, then continue
 	 * as if nothing happened.
 	 */
-	KASSERT(drive >= 0);
-
-	mvsata_hold(mvport);
 
-	/*
-	 * When running NCQ commands, READ LOG EXT is necessary to clear the
-	 * error condition and unblock the device.
-	 */
-	error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err);
-
-	mvsata_unhold(mvport);
+#if 0
+	XXX recheck - mvsata used to have this to handle the successful
+	recovery via read_log_ext:
 
-	switch (error) {
-	case 0:
-		/* Error out the particular NCQ xfer, then requeue the others */
-		if ((mvport->port_quetagidx & (1 << eslot)) != 0) {
 			xfer = ata_queue_hwslot_to_xfer(chp, eslot);
 			xfer->c_flags |= C_RECOVERED;
 			xfer->c_bio.error = ERROR;
 			xfer->c_bio.r_error = err;
 			xfer->ops->c_intr(chp, xfer, 1);
-		}
-		break;
-
-	case EOPNOTSUPP:
-		/*
-		 * Non-NCQ command error, just find the slot and end it with
-		 * an error. Handler figures the error itself.
-		 */
-		for (slot = 0; slot < MVSATA_EDMAQ_LEN; slot++) {
-			if ((mvport->port_quetagidx & (1 << slot)) != 0) {
-				xfer = ata_queue_hwslot_to_xfer(chp, slot);
-				if (xfer->c_drive != drive)
-					continue;
-
-				xfer->ops->c_intr(chp, xfer, 1);
-			}
-		}
-		break;
-
-	case EAGAIN:
-		/*
-		 * Failed to get resources to run the recovery command, must
-		 * reset the drive. This will also kill all still outstanding
-		 * transfers.
-		 */
-		ata_channel_lock(chp);
-		mvsata_reset_channel(chp, AT_POLL);
-		ata_channel_unlock(chp);
-		goto out;
-		/* NOTREACHED */
-
-	default:
-		/*
-		 * The command to get the slot failed. Kill outstanding
-		 * commands for the same drive only. No need to reset
-		 * the drive, it's unblocked nevertheless.
-		 */
-		break;
-	}
-
-	/* Requeue the non-errorred commands */ 
-	for (slot = 0; slot < MVSATA_EDMAQ_LEN; slot++) {
-		if (((mvport->port_quetagidx >> slot) & 1) == 0)
-			continue;
-
-		xfer = ata_queue_hwslot_to_xfer(chp, slot);
-		if (xfer->c_drive != drive)
-			continue;
-
-		xfer->ops->c_kill_xfer(chp, xfer,
-		    (error == 0) ? KILL_REQUEUE : KILL_RESET);
-	}
+#endif
+	ata_recovery_resume(chp, drive, tfd, AT_POLL);
 
-out:
 	/* Drive unblocked, back to normal operation */
-	mvport->port_recovering = false;
+	ata_channel_lock(chp);
+	CLR(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
+
 	atastart(chp);
 }
 
@@ -1129,8 +1051,6 @@ mvsata_bio_start(struct ata_channel *chp
 
 	ata_channel_lock_owned(chp);
 
-	mvsata_quetag_get(mvport, xfer->c_slot);
-
 	if (xfer->c_flags & C_DMA)
 		if (drvp->n_xfers <= NXFER)
 			drvp->n_xfers++;
@@ -1521,10 +1441,8 @@ mvsata_bio_kill_xfer(struct ata_channel 
 	}
 	ata_bio->r_error = WDCE_ABRT;
 
-	if (deactivate) {
-		mvsata_quetag_put(mvport, xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 
 	(*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer);
 }
@@ -1555,7 +1473,6 @@ mvsata_bio_done(struct ata_channel *chp,
 	ata_bio->bcount = xfer->c_bcount;
 
 	/* mark controller inactive and free xfer */
-	mvsata_quetag_put(mvport, xfer->c_slot);
 	ata_deactivate_xfer(chp, xfer);
 
 	ata_bio->flags |= ATA_ITSDONE;
@@ -1741,8 +1658,6 @@ mvsata_wdc_cmd_start(struct ata_channel 
 
 	ata_channel_lock_owned(chp);
 
-	mvsata_quetag_get(mvport, xfer->c_slot);
-
 	/* First, EDMA disable, if enabled this channel. */
 	KASSERT((chp->ch_flags & ATACH_NCQ) == 0);
 	if (mvport->port_edmamode_curr != nodma)
@@ -1951,10 +1866,8 @@ mvsata_wdc_cmd_kill_xfer(struct ata_chan
 
 	mvsata_wdc_cmd_done_end(chp, xfer);
 
-	if (deactivate) {
-		mvsata_quetag_put(mvport, xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 }
 
 static void
@@ -2014,8 +1927,6 @@ mvsata_wdc_cmd_done(struct ata_channel *
 		}
 	}
 
-	mvsata_quetag_put(mvport, xfer->c_slot);
-
 	if (ata_c->flags & AT_POLL) {
 		/* enable interrupts */
 		MVSATA_WDC_WRITE_1(mvport, SRB_CAS, WDCTL_4BIT);
@@ -2127,8 +2038,6 @@ mvsata_atapi_start(struct ata_channel *c
 
 	ata_channel_lock_owned(chp);
 
-	mvsata_quetag_get(mvport, xfer->c_slot);
-
 	KASSERT((chp->ch_flags  & ATACH_NCQ) == 0);
 	if (mvport->port_edmamode_curr != nodma)
 		mvsata_edma_disable(mvport, 10 /* ms */, wait_flags);
@@ -2566,10 +2475,8 @@ mvsata_atapi_kill_xfer(struct ata_channe
 		panic("mvsata_atapi_kill_xfer");
 	}
 
-	if (deactivate) {
-		mvsata_quetag_put(mvport, xfer->c_slot);
+	if (deactivate)
 		ata_deactivate_xfer(chp, xfer);
-	}
 
 	ata_free_xfer(chp, xfer);
 	scsipi_done(sc_xfer);
@@ -2696,7 +2603,6 @@ mvsata_atapi_phase_complete(struct ata_x
 static void
 mvsata_atapi_done(struct ata_channel *chp, struct ata_xfer *xfer)
 {
-	struct mvsata_port *mvport = (struct mvsata_port *)chp;
 	struct scsipi_xfer *sc_xfer = xfer->c_scsipi;
 	bool iserror = (sc_xfer->error != XS_NOERROR);
 
@@ -2709,7 +2615,6 @@ mvsata_atapi_done(struct ata_channel *ch
 		return;
 
 	/* mark controller inactive and free the command */
-	mvsata_quetag_put(mvport, xfer->c_slot);
 	ata_deactivate_xfer(chp, xfer);
 
 	ata_free_xfer(chp, xfer);
@@ -2887,6 +2792,8 @@ mvsata_edma_handle(struct mvsata_port *m
 		    erpqop * sizeof(struct crpb),
 		    n * sizeof(struct crpb), BUS_DMASYNC_POSTREAD);
 
+	uint32_t aslots = ata_queue_active(chp);
+
 	prev_erpqop = erpqop;
 	while (erpqop != erpqip) {
 #ifdef MVSATA_DEBUG
@@ -2898,7 +2805,7 @@ mvsata_edma_handle(struct mvsata_port *m
 
 		quetag = CRPB_CHOSTQUETAG(le16toh(crpb->id));
 
-		if ((mvport->port_quetagidx & __BIT(quetag)) == 0) {
+		if ((aslots & __BIT(quetag)) == 0) {
 			/* not actually executing */
 			continue;
 		}
@@ -2987,10 +2894,12 @@ mvsata_edma_rqq_remove(struct mvsata_por
 	bus_dmamap_sync(mvport->port_dmat, mvport->port_crqb_dmamap, 0,
 	    sizeof(union mvsata_crqb) * MVSATA_EDMAQ_LEN, BUS_DMASYNC_PREWRITE);
 
+	uint32_t aslots = ata_queue_active(chp);
+
 	for (i = 0, erqqip = 0; i < MVSATA_EDMAQ_LEN; i++) {
 		struct ata_xfer *rqxfer;
 
-		if ((mvport->port_quetagidx & __BIT(i)) == 0)
+		if ((aslots & __BIT(i)) == 0)
 			continue;
 
 		if (i == xfer->c_slot) {
@@ -3294,30 +3203,6 @@ mvsata_wdc_reg_init(struct mvsata_port *
 
 
 #ifndef MVSATA_WITHOUTDMA
-/*
- * There are functions to remember Host Queue Tag.
- */
-
-static inline void
-mvsata_quetag_get(struct mvsata_port *mvport, uint8_t quetag)
-{
-	KASSERT(quetag <= 32);
-
-	/*
-	 * Do not check whether it's already set, can happen when
-	 * postponing bio or atapi xfer to thread.
-	 */
-	mvport->port_quetagidx |= __BIT(quetag);
-}
-
-static inline void
-mvsata_quetag_put(struct mvsata_port *mvport, uint8_t quetag)
-{
-	KASSERT(quetag <= 32);
-	KASSERT((mvport->port_quetagidx & __BIT(quetag)) != 0);
-	mvport->port_quetagidx &= ~__BIT(quetag);
-}
-
 static void *
 mvsata_edma_resource_prepare(struct mvsata_port *mvport, bus_dma_tag_t dmat,
 			     bus_dmamap_t *dmamap, size_t size, int write)

Index: src/sys/dev/ic/mvsatavar.h
diff -u src/sys/dev/ic/mvsatavar.h:1.4 src/sys/dev/ic/mvsatavar.h:1.4.2.1
--- src/sys/dev/ic/mvsatavar.h:1.4	Fri Aug 31 18:43:29 2018
+++ src/sys/dev/ic/mvsatavar.h	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: mvsatavar.h,v 1.4 2018/08/31 18:43:29 jdolecek Exp $	*/
+/*	$NetBSD: mvsatavar.h,v 1.4.2.1 2018/10/11 20:57:51 jdolecek Exp $	*/
 /*
  * Copyright (c) 2008 KIYOHARA Takashi
  * All rights reserved.
@@ -73,8 +73,6 @@ struct mvsata_port {
 	enum mvsata_edmamode port_edmamode_negotiated;
 	enum mvsata_edmamode port_edmamode_curr;
 
-	volatile uint32_t port_quetagidx;	/* Host Queue Tag valid */
-
 	int port_prev_erqqop;		/* previous Req Queue Out-Pointer */
 	bus_dma_tag_t port_dmat;
 	union mvsata_crqb *port_crqb;	/* EDMA Command Request Block */
@@ -96,10 +94,6 @@ struct mvsata_port {
 	bus_space_handle_t port_sata_sstatus;	/* SATA Interface status reg */
 
 	struct _fix_phy_param _fix_phy_param;
-
-	/* Recovery context */
-	uint32_t port_hold_slots;
-	bool port_recovering;
 };
 
 struct mvsata_hc {

Index: src/sys/dev/ic/siisata.c
diff -u src/sys/dev/ic/siisata.c:1.35.6.8 src/sys/dev/ic/siisata.c:1.35.6.9
--- src/sys/dev/ic/siisata.c:1.35.6.8	Sun Oct  7 15:42:47 2018
+++ src/sys/dev/ic/siisata.c	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: siisata.c,v 1.35.6.8 2018/10/07 15:42:47 jdolecek Exp $ */
+/* $NetBSD: siisata.c,v 1.35.6.9 2018/10/11 20:57:51 jdolecek Exp $ */
 
 /* from ahcisata_core.c */
 
@@ -79,7 +79,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: siisata.c,v 1.35.6.8 2018/10/07 15:42:47 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: siisata.c,v 1.35.6.9 2018/10/11 20:57:51 jdolecek Exp $");
 
 #include <sys/types.h>
 #include <sys/param.h>
@@ -535,7 +535,7 @@ siisata_intr_port(struct siisata_channel
 			 * We don't expect the recovery to trigger error,
 			 * but handle this just in case.
 			 */
-			if (!schp->sch_recovering) 
+			if (!ISSET(chp->ch_flags, ATACH_RECOVERING)) 
 				recover = true;
 			else {
 				aprint_error_dev(sc->sc_atac.atac_dev,
@@ -574,7 +574,7 @@ process:
 		 * can activate another command(s), so must only process
 		 * commands active before we start processing.
 		 */
-		uint32_t aslots = schp->sch_active_slots;
+		uint32_t aslots = ata_queue_active(chp);
 
 		for (int slot=0; slot < SIISATA_MAX_SLOTS; slot++) {
 			if ((aslots & __BIT(slot)) != 0 &&
@@ -591,20 +591,6 @@ process:
 	}
 }
 
-static void
-siisata_hold(struct siisata_channel *schp)
-{
-	schp->sch_hold_slots |= schp->sch_active_slots;
-	schp->sch_active_slots = 0;
-}
-
-static void
-siisata_unhold(struct siisata_channel *schp)
-{
-	schp->sch_active_slots = schp->sch_hold_slots;
-	schp->sch_hold_slots = 0;
-}
-
 /* Recover channel after transfer aborted */
 void
 siisata_channel_recover(struct ata_channel *chp, uint32_t tfd)
@@ -612,14 +598,12 @@ siisata_channel_recover(struct ata_chann
 	struct siisata_channel *schp = (struct siisata_channel *)chp;
 	struct siisata_softc *sc =
 	    (struct siisata_softc *)schp->ata_channel.ch_atac;
-	struct ata_drive_datas *drvp;
-	int drive, error;
-	uint8_t eslot, slot, st, err;
-	struct ata_xfer *xfer;
-
-	KASSERT(!schp->sch_recovering);
+	int drive;
 
-	schp->sch_recovering = true;
+	ata_channel_lock(chp);
+	KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING));
+	SET(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
 
 	if (chp->ch_ndrives > PMP_PORT_CTL) {
 		/* Get PM port number for the device in error */
@@ -628,8 +612,6 @@ siisata_channel_recover(struct ata_chann
 	} else
 		drive = 0;
 
-	drvp = &chp->ch_drive[drive];
-
 	/*
 	 * If BSY or DRQ bits are set, must execute COMRESET to return
 	 * device to idle state. Otherwise, commands can be reissued
@@ -637,86 +619,24 @@ siisata_channel_recover(struct ata_chann
 	 * READ LOG EXT for NCQ to unblock device processing if COMRESET
 	 * was not done.
 	 */
-	if ((ATACH_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0)
-		goto reset;
-
-	KASSERT(drive >= 0);
-	siisata_reinit_port(chp, drive);
-
-	siisata_hold(schp);
-
-	/*
-	 * When running NCQ commands, READ LOG EXT is necessary to clear the
-	 * error condition and unblock the device.
-	 */
-	error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err);
-
-	siisata_unhold(schp);
-
-	switch (error) {
-	case 0:
-		/* Error out the particular NCQ xfer, then requeue the others */
-		if ((schp->sch_active_slots & (1 << eslot)) != 0) {
-			xfer = ata_queue_hwslot_to_xfer(chp, eslot);
-			xfer->c_flags |= C_RECOVERED;
-			xfer->ops->c_intr(chp, xfer, ATACH_ERR_ST(err, st));
-		}
-		break;
-
-	case EOPNOTSUPP:
-		/*
-		 * Non-NCQ command error, just find the slot and end it with
-		 * the error.
-		 */
-		for (slot = 0; slot < SIISATA_MAX_SLOTS; slot++) {
-			if ((schp->sch_active_slots & (1 << slot)) != 0) {
-				xfer = ata_queue_hwslot_to_xfer(chp, slot);
-				if (xfer->c_drive != drive)
-					continue;
-
-				xfer->ops->c_intr(chp, xfer, tfd);
-			}
-		}
-		break;
-
-	case EAGAIN:
-		/*
-		 * Failed to get resources to run the recovery command, must
-		 * reset the drive. This will also kill all still outstanding
-		 * transfers.
-		 */
-reset:
+	if ((ATACH_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) {
 		ata_channel_lock(chp);
 		siisata_device_reset(chp);
 		ata_channel_unlock(chp);
 		goto out;
-		/* NOTREACHED */
-
-	default:
-		/*
-		 * The command to get the slot failed. Kill outstanding
-		 * commands for the same drive only. No need to reset
-		 * the drive, it's unblocked nevertheless.
-		 */
-		break;
 	}
 
-	/* Requeue the non-errorred commands */ 
-	for (slot = 0; slot < SIISATA_MAX_SLOTS; slot++) {
-		if (((schp->sch_active_slots >> slot) & 1) == 0)
-			continue;
+	KASSERT(drive >= 0);
+	siisata_reinit_port(chp, drive);
 
-		xfer = ata_queue_hwslot_to_xfer(chp, slot);
-		if (xfer->c_drive != drive)
-			continue;
-
-		xfer->ops->c_kill_xfer(chp, xfer,
-		    (error == 0) ? KILL_REQUEUE : KILL_RESET);
-	}
+	ata_recovery_resume(chp, drive, tfd, AT_POLL);
 
 out:
 	/* Drive unblocked, back to normal operation */
-	schp->sch_recovering = false;
+	ata_channel_lock(chp);
+	CLR(chp->ch_flags, ATACH_RECOVERING);
+	ata_channel_unlock(chp);
+
 	atastart(chp);
 }
 
@@ -1493,12 +1413,7 @@ siisata_activate_prb(struct siisata_chan
 
 	sc = (struct siisata_softc *)schp->ata_channel.ch_atac;
 
-	KASSERTMSG((schp->sch_active_slots & __BIT(slot)) == 0,
-	    "%s: trying to activate active slot %d", SIISATANAME(sc), slot);
-
 	SIISATA_PRB_SYNC(sc, schp, slot, BUS_DMASYNC_PREWRITE);
-	/* keep track of what's going on */
-	schp->sch_active_slots |= __BIT(slot);
 
 	offset = PRO_CARX(schp->ata_channel.ch_channel, slot);
 
@@ -1515,11 +1430,6 @@ siisata_deactivate_prb(struct siisata_ch
 
 	sc = (struct siisata_softc *)schp->ata_channel.ch_atac;
 
-	KASSERTMSG((schp->sch_active_slots & __BIT(slot)) != 0,
-	    "%s: trying to deactivate inactive slot %d", SIISATANAME(sc),
-	    slot);
-
-	schp->sch_active_slots &= ~__BIT(slot); /* mark free */
 	SIISATA_PRB_SYNC(sc, schp, slot, BUS_DMASYNC_POSTWRITE);
 }
 

Index: src/sys/dev/ic/siisatavar.h
diff -u src/sys/dev/ic/siisatavar.h:1.7 src/sys/dev/ic/siisatavar.h:1.7.6.1
--- src/sys/dev/ic/siisatavar.h:1.7	Sat Oct  7 16:05:32 2017
+++ src/sys/dev/ic/siisatavar.h	Thu Oct 11 20:57:51 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: siisatavar.h,v 1.7 2017/10/07 16:05:32 jdolecek Exp $ */
+/* $NetBSD: siisatavar.h,v 1.7.6.1 2018/10/11 20:57:51 jdolecek Exp $ */
 
 /* from ahcisatavar.h */
 
@@ -99,10 +99,6 @@ struct siisata_softc {
 		bus_addr_t sch_bus_prb[SIISATA_MAX_SLOTS];
 
 		bus_dmamap_t sch_datad[SIISATA_MAX_SLOTS];
-
-		volatile uint32_t sch_active_slots;
-		uint32_t sch_hold_slots;
-		bool sch_recovering;
 	} sc_channels[SIISATA_MAX_PORTS];
 };
 

Added files:

Index: src/sys/dev/ata/ata_recovery.c
diff -u /dev/null src/sys/dev/ata/ata_recovery.c:1.1.2.1
--- /dev/null	Thu Oct 11 20:57:51 2018
+++ src/sys/dev/ata/ata_recovery.c	Thu Oct 11 20:57:51 2018
@@ -0,0 +1,247 @@
+/*	$NetBSD: ata_recovery.c,v 1.1.2.1 2018/10/11 20:57:51 jdolecek Exp $	*/
+
+/*-
+ * Copyright (c) 2018 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: ata_recovery.c,v 1.1.2.1 2018/10/11 20:57:51 jdolecek Exp $");
+
+#include "opt_ata.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/proc.h>
+#include <sys/kthread.h>
+#include <sys/errno.h>
+#include <sys/ataio.h>
+#include <sys/kmem.h>
+#include <sys/intr.h>
+#include <sys/bus.h>
+#include <sys/bitops.h>
+
+#include <dev/ata/ataconf.h>
+#include <dev/ata/atareg.h>
+#include <dev/ata/atavar.h>
+
+#define DEBUG_FUNCS  0x08
+#define DEBUG_PROBE  0x10
+#define DEBUG_DETACH 0x20
+#define	DEBUG_XFERS  0x40
+#ifdef ATADEBUG
+extern int atadebug_mask;
+#define ATADEBUG_PRINT(args, level) \
+	if (atadebug_mask & (level)) \
+		printf args
+#else
+#define ATADEBUG_PRINT(args, level)
+#endif
+
+int
+ata_read_log_ext_ncq(struct ata_drive_datas *drvp, uint8_t flags,
+    uint8_t *slot, uint8_t *status, uint8_t *err)
+{
+	struct ata_xfer *xfer = &drvp->recovery_xfer;
+	int rv;
+	struct ata_channel *chp = drvp->chnl_softc;
+	struct atac_softc *atac = chp->ch_atac;
+	uint8_t *tb, cksum, page;
+
+	ATADEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS);
+
+	/* Only NCQ ATA drives support/need this */
+	if (drvp->drive_type != ATA_DRIVET_ATA ||
+	    (drvp->drive_flags & ATA_DRIVE_NCQ) == 0)
+		return EOPNOTSUPP;
+
+	memset(xfer, 0, sizeof(*xfer));
+
+	tb = drvp->recovery_blk;
+	memset(tb, 0, sizeof(drvp->recovery_blk));
+
+	/*
+	 * We could use READ LOG DMA EXT if drive supports it (i.e.
+	 * when it supports Streaming feature) to avoid PIO command,
+	 * and to make this a little faster. Realistically, it
+	 * should not matter.
+	 */
+	xfer->c_flags |= C_SKIP_QUEUE;
+	xfer->c_ata_c.r_command = WDCC_READ_LOG_EXT;
+	xfer->c_ata_c.r_lba = page = WDCC_LOG_PAGE_NCQ;
+	xfer->c_ata_c.r_st_bmask = WDCS_DRDY;
+	xfer->c_ata_c.r_st_pmask = WDCS_DRDY;
+	xfer->c_ata_c.r_count = 1;
+	xfer->c_ata_c.r_device = WDSD_LBA;
+	xfer->c_ata_c.flags = AT_READ | AT_LBA | AT_LBA48 | flags;
+	xfer->c_ata_c.timeout = 1000; /* 1s */
+	xfer->c_ata_c.data = tb;
+	xfer->c_ata_c.bcount = sizeof(drvp->recovery_blk);
+
+	if ((*atac->atac_bustype_ata->ata_exec_command)(drvp,
+						xfer) != ATACMD_COMPLETE) {
+		rv = EAGAIN;
+		goto out;
+	}
+	if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) {
+		rv = EINVAL;
+		goto out;
+	}
+
+	cksum = 0;
+	for (int i = 0; i < sizeof(drvp->recovery_blk); i++)
+		cksum += tb[i];
+	if (cksum != 0) {
+		device_printf(drvp->drv_softc,
+		    "invalid checksum %x for READ LOG EXT page %x\n",
+		    cksum, page);
+		rv = EINVAL;
+		goto out;
+	}
+
+	if (tb[0] & WDCC_LOG_NQ) {
+		/* not queued command */
+		rv = EOPNOTSUPP;
+		goto out;
+	}
+
+	*slot = tb[0] & 0x1f;
+	*status = tb[2];
+	*err = tb[3];
+
+	if ((*status & WDCS_ERR) == 0) {
+		/*
+		 * We expect error here. Normal physical drives always
+		 * do, it's part of ATA standard. However, QEMU AHCI emulation
+		 * mishandles READ LOG EXT in a way that the command itself
+		 * returns without error, but no data is transferred.
+		 */
+		device_printf(drvp->drv_softc,
+		    "READ LOG EXT page %x failed to report error: "
+		    "slot %d err %x status %x\n",
+		    page, *slot, *err, *status);
+		rv = EOPNOTSUPP;
+		goto out;
+	}
+
+	rv = 0;
+
+out:
+	return rv;
+}
+
+/*
+ * Must be called without channel lock, and with interrupts blocked.
+ */
+void
+ata_recovery_resume(struct ata_channel *chp, int drive, int tfd, int flags)
+{
+	struct ata_drive_datas *drvp;
+	uint8_t slot, eslot, st, err;
+	int error;
+	struct ata_xfer *xfer;
+	const uint8_t ch_openings = ata_queue_openings(chp);
+
+	ata_channel_lock(chp);
+	ata_queue_hold(chp);
+	ata_channel_unlock(chp);
+
+	KASSERT(drive < chp->ch_ndrives);
+	drvp = &chp->ch_drive[drive];
+
+	/*
+	 * When running NCQ commands, READ LOG EXT is necessary to clear the
+	 * error condition and unblock the device.
+	 */
+	error = ata_read_log_ext_ncq(drvp, flags, &eslot, &st, &err);
+
+	ata_channel_lock(chp);
+	ata_queue_unhold(chp);
+	ata_channel_unlock(chp);
+
+	switch (error) {
+	case 0:
+		/* Error out the particular NCQ xfer, then requeue the others */
+		if ((ata_queue_active(chp) & (1U << eslot)) != 0) {
+			xfer = ata_queue_hwslot_to_xfer(chp, eslot);
+			xfer->c_flags |= C_RECOVERED;
+			xfer->ops->c_intr(chp, xfer, ATACH_ERR_ST(err, st));
+		}
+		break;
+
+	case EOPNOTSUPP:
+		/*
+		 * Non-NCQ command error, just find the slot and end with
+		 * the error.
+		 */
+		for (slot = 0; slot < ch_openings; slot++) {
+			if ((ata_queue_active(chp) & (1U << slot)) != 0) {
+				xfer = ata_queue_hwslot_to_xfer(chp, slot);
+				xfer->ops->c_intr(chp, xfer, tfd);
+			}
+		}
+		break;
+
+	case EAGAIN:
+		/*
+		 * Failed to get resources to run the recovery command, must
+		 * reset the drive. This will also kill all still outstanding
+		 * transfers.
+		 */
+		ata_channel_lock(chp);
+		ata_thread_run(chp, ATACH_TH_RESET, ATACH_NODRIVE, flags);
+		ata_channel_unlock(chp);
+		goto out;
+		/* NOTREACHED */
+
+	default:
+		/*
+		 * The command to get the slot failed. Kill outstanding
+		 * commands for the same drive only. No need to reset
+		 * the drive, it's unblocked nevertheless.
+		 */
+		break;
+	}
+
+	/* Requeue all unfinished commands for same drive as failed command */ 
+	for (slot = 0; slot < ch_openings; slot++) {
+		if ((ata_queue_active(chp) & (1U << slot)) == 0)
+			continue;
+
+		xfer = ata_queue_hwslot_to_xfer(chp, slot);
+		if (drive != xfer->c_drive)
+			continue;
+
+		xfer->ops->c_kill_xfer(chp, xfer,
+		    (error == 0) ? KILL_REQUEUE : KILL_RESET);
+	}
+
+out:
+	/* Nothing more to do */
+	return;
+}

Reply via email to