Source-Changes-HG archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
[src/jdolecek-ncq]: src/sys/dev/ata tighen and expand error handling, mostly ...
details: https://anonhg.NetBSD.org/src/rev/acf628dccbcf
branches: jdolecek-ncq
changeset: 352708:acf628dccbcf
user: jdolecek <jdolecek%NetBSD.org@localhost>
date: Wed Jul 19 19:39:28 2017 +0000
description:
tighen and expand error handling, mostly for NCQ use cases:
- make retry timeout callout per xfer, i.e. retry separately
- zero whole bio struct on retry to avoid more stale state
- add a REQUEUE option, which doesn't bump retry count
- add ata_read_log_ext_ncq() for NCQ recovery
- adjust logic for activating xfers - allow next command only when
it's for same drive, several concurrent are only supported when HBA
and driver support FIS-based switching
- add new ata_timeout() which handles race between callout_stop()
and the invokation, add appropriate handling on deactivate/free paths
- stop using ch_status/ch_error in non-wdc code; later it will be dropped
completely
diffstat:
sys/dev/ata/ata.c | 250 +++++++++++++++++++++++++++++++++++++++-----
sys/dev/ata/atareg.h | 9 +-
sys/dev/ata/atavar.h | 33 ++++-
sys/dev/ata/satafis_subr.c | 9 +-
sys/dev/ata/satafisvar.h | 4 +-
sys/dev/ata/wd.c | 93 ++++++++--------
sys/dev/ata/wdvar.h | 5 +-
7 files changed, 304 insertions(+), 99 deletions(-)
diffs (truncated from 833 to 300 lines):
diff -r 80984bccc40c -r acf628dccbcf sys/dev/ata/ata.c
--- a/sys/dev/ata/ata.c Sun Jul 16 21:41:47 2017 +0000
+++ b/sys/dev/ata/ata.c Wed Jul 19 19:39:28 2017 +0000
@@ -1,4 +1,4 @@
-/* $NetBSD: ata.c,v 1.132.8.18 2017/06/27 18:36:03 jdolecek Exp $ */
+/* $NetBSD: ata.c,v 1.132.8.19 2017/07/19 19:39:28 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.132.8.18 2017/06/27 18:36:03 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ata.c,v 1.132.8.19 2017/07/19 19:39:28 jdolecek Exp $");
#include "opt_ata.h"
@@ -241,14 +241,34 @@
return xfer;
}
-static void
-ata_xfer_init(struct ata_xfer *xfer, bool zero)
+struct ata_xfer *
+ata_queue_drive_active_xfer(struct ata_channel *chp, int drive)
{
- if (zero)
- memset(xfer, 0, sizeof(*xfer));
+ struct ata_xfer *xfer = NULL;
+
+ mutex_enter(&chp->ch_lock);
+
+ TAILQ_FOREACH(xfer, &chp->ch_queue->active_xfers, c_activechain) {
+ if (xfer->c_drive == drive)
+ break;
+ }
+ KASSERT(xfer != NULL);
+
+ mutex_exit(&chp->ch_lock);
+
+ return xfer;
+}
+
+static void
+ata_xfer_init(struct ata_xfer *xfer, uint8_t slot)
+{
+ memset(xfer, 0, sizeof(*xfer));
+
+ xfer->c_slot = slot;
cv_init(&xfer->c_active, "ataact");
callout_init(&xfer->c_timo_callout, 0); /* XXX MPSAFE */
+ callout_init(&xfer->c_retry_callout, 0); /* XXX MPSAFE */
}
static void
@@ -256,6 +276,8 @@
{
callout_halt(&xfer->c_timo_callout, NULL); /* XXX MPSAFE */
callout_destroy(&xfer->c_timo_callout);
+ callout_halt(&xfer->c_retry_callout, NULL); /* XXX MPSAFE */
+ callout_destroy(&xfer->c_retry_callout);
cv_destroy(&xfer->c_active);
}
@@ -278,7 +300,7 @@
cv_init(&chq->queue_drain, "atdrn");
for (uint8_t i = 0; i < openings; i++)
- ata_xfer_init(&chq->queue_xfers[i], false);
+ ata_xfer_init(&chq->queue_xfers[i], i);
return chq;
}
@@ -1009,6 +1031,88 @@
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;
+ int rv;
+ struct ata_channel *chp = drvp->chnl_softc;
+ struct atac_softc *atac = chp->ch_atac;
+ uint8_t *tb;
+
+ 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;
+
+ xfer = ata_get_xfer_ext(chp, false, 0);
+ if (xfer == NULL) {
+ ATADEBUG_PRINT(("%s: no xfer\n", __func__),
+ DEBUG_FUNCS|DEBUG_XFERS);
+ return EAGAIN;
+ }
+
+ tb = malloc(DEV_BSIZE, M_DEVBUF, M_NOWAIT);
+ if (tb == NULL) {
+ ATADEBUG_PRINT(("%s: memory allocation failed\n", __func__),
+ DEBUG_FUNCS|DEBUG_XFERS);
+ rv = EAGAIN;
+ goto out;
+ }
+ memset(tb, 0, DEV_BSIZE);
+
+ /*
+ * 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_IMMEDIATE;
+ xfer->c_ata_c.r_command = WDCC_READ_LOG_EXT;
+ xfer->c_ata_c.r_lba = 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 | flags;
+ xfer->c_ata_c.timeout = 1000; /* 1s */
+ xfer->c_ata_c.data = tb;
+ xfer->c_ata_c.bcount = DEV_BSIZE;
+
+ if ((*atac->atac_bustype_ata->ata_exec_command)(drvp,
+ xfer) != ATACMD_COMPLETE) {
+ rv = EAGAIN;
+ goto out2;
+ }
+ if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) {
+ rv = EINVAL;
+ goto out2;
+ }
+
+ /* XXX verify checksum and refuse if not correct (QEMU) */
+
+ if (tb[0] & WDCC_LOG_NQ) {
+ /* not a NCQ command */
+ rv = EOPNOTSUPP;
+ goto out2;
+ }
+
+ *slot = tb[0] & 0x1f;
+ *status = tb[2];
+ *err = tb[3];
+
+ rv = 0;
+
+out2:
+ free(tb, DEV_BSIZE);
+out:
+ ata_free_xfer(chp, xfer);
+ return rv;
+}
+
#if NATA_DMA
void
ata_dmaerr(struct ata_drive_datas *drvp, int flags)
@@ -1067,8 +1171,13 @@
mutex_enter(&chp->ch_lock);
- /* insert at the end of command list */
- TAILQ_INSERT_TAIL(&chp->ch_queue->queue_xfer, xfer, c_xferchain);
+ /* insert at the end of command list unless specially requested */
+ if (xfer->c_flags & C_IMMEDIATE)
+ TAILQ_INSERT_HEAD(&chp->ch_queue->queue_xfer, xfer,
+ c_xferchain);
+ else
+ TAILQ_INSERT_TAIL(&chp->ch_queue->queue_xfer, xfer,
+ c_xferchain);
ATADEBUG_PRINT(("atastart from ata_exec_xfer, flags 0x%x\n",
chp->ch_flags), DEBUG_XFERS);
@@ -1086,7 +1195,7 @@
* Free xfer now if it there was attempt to free it
* while we were waiting.
*/
- if (xfer->c_flags & C_FREE) {
+ if ((xfer->c_flags & (C_FREE|C_WAITTIMO)) == C_FREE) {
ata_free_xfer(chp, xfer);
return;
}
@@ -1111,6 +1220,7 @@
struct atac_softc *atac = chp->ch_atac;
struct ata_queue *chq = chp->ch_queue;
struct ata_xfer *xfer, *axfer;
+ bool immediate;
#ifdef ATA_DEBUG
int spl1, spl2;
@@ -1127,12 +1237,19 @@
mutex_enter(&chp->ch_lock);
+ KASSERT(chq->queue_active <= chq->queue_openings);
if (chq->queue_active == chq->queue_openings) {
goto out; /* channel completely busy */
}
+ /* is there a xfer ? */
+ if ((xfer = TAILQ_FIRST(&chp->ch_queue->queue_xfer)) == NULL)
+ goto out;
+
+ immediate = ISSET(xfer->c_flags, C_IMMEDIATE);
+
/* is the queue frozen? */
- if (__predict_false(chq->queue_freeze > 0)) {
+ if (__predict_false(!immediate && chq->queue_freeze > 0)) {
if (chq->queue_flags & QF_IDLE_WAIT) {
chq->queue_flags &= ~QF_IDLE_WAIT;
wakeup(&chq->queue_flags);
@@ -1140,21 +1257,23 @@
goto out; /* queue frozen */
}
- /* is there a xfer ? */
- if ((xfer = TAILQ_FIRST(&chp->ch_queue->queue_xfer)) == NULL)
- goto out;
-
/* all xfers on same queue must belong to the same channel */
KASSERT(xfer->c_chp == chp);
/*
- * Can only take NCQ command if there are no current active
- * commands, or if the active commands are NCQ. Need only check
- * first xfer.
+ * Can only take the command if there are no current active
+ * commands, or if the command is NCQ and the active commands are also
+ * NCQ. If PM is in use and HBA driver doesn't support/use FIS-based
+ * switching, can only send commands to single drive.
+ * Need only check first xfer.
+ * XXX FIS-based switching - revisit
*/
- axfer = TAILQ_FIRST(&chp->ch_queue->active_xfers);
- if (axfer && (axfer->c_flags & C_NCQ) == 0)
- goto out;
+ if (!immediate && (axfer = TAILQ_FIRST(&chp->ch_queue->active_xfers))) {
+ if (!ISSET(xfer->c_flags, C_NCQ) ||
+ !ISSET(axfer->c_flags, C_NCQ) ||
+ xfer->c_drive != axfer->c_drive)
+ goto out;
+ }
struct ata_drive_datas * const drvp = &chp->ch_drive[xfer->c_drive];
@@ -1170,11 +1289,6 @@
goto out;
}
-#ifdef DIAGNOSTIC
- if ((chp->ch_flags & ATACH_IRQ_WAIT) != 0
- && chp->ch_queue->queue_openings == 1)
- panic("atastart: channel waiting for irq");
-#endif
ATADEBUG_PRINT(("atastart: xfer %p channel %d drive %d\n", xfer,
chp->ch_channel, xfer->c_drive), DEBUG_XFERS);
if (drvp->drive_flags & ATA_DRIVE_RESET) {
@@ -1242,7 +1356,6 @@
/* zero everything after the callout member */
memset(&xfer->c_startzero, 0,
sizeof(struct ata_xfer) - offsetof(struct ata_xfer, c_startzero));
- xfer->c_slot = slot;
out:
mutex_exit(&chp->ch_lock);
@@ -1259,7 +1372,7 @@
mutex_enter(&chp->ch_lock);
- if (xfer->c_flags & C_WAITACT) {
+ if (xfer->c_flags & (C_WAITACT|C_WAITTIMO)) {
/* Someone is waiting for this xfer, so we can't free now */
xfer->c_flags |= C_FREE;
cv_signal(&xfer->c_active);
@@ -1318,6 +1431,9 @@
callout_stop(&xfer->c_timo_callout);
+ if (callout_invoking(&xfer->c_timo_callout))
+ xfer->c_flags |= C_WAITTIMO;
+
TAILQ_REMOVE(&chq->active_xfers, xfer, c_activechain);
chq->active_xfers_used &= ~__BIT(xfer->c_slot);
chq->queue_active--;
@@ -1359,6 +1475,76 @@
}
/*
+ * Check for race of normal transfer handling vs. timeout.
+ */
+static bool
+ata_timo_xfer_check(struct ata_xfer *xfer)
+{
Home |
Main Index |
Thread Index |
Old Index