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/d74cecbeabf3
branches:  jdolecek-ncq
changeset: 822959:d74cecbeabf3
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 2cf5ce26640c -r d74cecbeabf3 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