summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorthorpej <thorpej@NetBSD.org>2003-03-21 22:40:56 +0000
committerthorpej <thorpej@NetBSD.org>2003-03-21 22:40:56 +0000
commit3f2184def790cfd198da260d2d517019eb12a7ea (patch)
tree2c27db89761727402267fb8ef18e2abc51933ae4 /sys/dev
parent60418b39b7e0d248a35caa5300fe900e6eeb8c6f (diff)
Work around a buggy PHY in some Seagate S-ATA drives which get confused
by the way some S-ATA controllers packetize data when the sector count of the transfer mod 15 is 1.
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/ata/wd.c156
-rw-r--r--sys/dev/ata/wdvar.h3
2 files changed, 154 insertions, 5 deletions
diff --git a/sys/dev/ata/wd.c b/sys/dev/ata/wd.c
index 194ef3327f2..2842b287cc6 100644
--- a/sys/dev/ata/wd.c
+++ b/sys/dev/ata/wd.c
@@ -1,4 +1,4 @@
-/* $NetBSD: wd.c,v 1.238 2003/02/23 08:50:58 yamt Exp $ */
+/* $NetBSD: wd.c,v 1.239 2003/03/21 22:40:56 thorpej Exp $ */
/*
* Copyright (c) 1998, 2001 Manuel Bouyer. All rights reserved.
@@ -66,7 +66,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: wd.c,v 1.238 2003/02/23 08:50:58 yamt Exp $");
+__KERNEL_RCSID(0, "$NetBSD: wd.c,v 1.239 2003/03/21 22:40:56 thorpej Exp $");
#ifndef WDCDEBUG
#define WDCDEBUG
@@ -200,6 +200,52 @@ static void bad144intern __P((struct wd_softc *));
int wdlock __P((struct wd_softc *));
void wdunlock __P((struct wd_softc *));
+#define WD_QUIRK_SPLIT_MOD15_WRITE 0x0001 /* must split certain writes */
+
+/*
+ * Quirk table for IDE drives. Put more-specific matches first, since
+ * a simple globbing routine is used for matching.
+ */
+static const struct wd_quirk {
+ const char *wdq_match; /* inquiry pattern to match */
+ int wdq_quirks; /* drive quirks */
+} wd_quirk_table[] = {
+ /*
+ * Some Seagate S-ATA drives have a PHY which can get confused
+ * with the way data is packetized by some S-ATA controllers.
+ *
+ * The work-around is to split in two any write transfer who's
+ * sector count % 15 == 1 (assuming 512 byte sectors).
+ *
+ * XXX This is an incomplete list. There are at least a couple
+ * XXX more model numbers. If you have trouble with such transfers
+ * XXX (8K is the most common) on Seagate S-ATA drives, please
+ * XXX notify thorpej@netbsd.org.
+ */
+ { "ST3120023AS",
+ WD_QUIRK_SPLIT_MOD15_WRITE },
+
+ { NULL,
+ 0 }
+};
+
+static const struct wd_quirk *
+wd_lookup_quirks(const char *name)
+{
+ const struct wd_quirk *wdq;
+ const char *estr;
+
+ for (wdq = wd_quirk_table; wdq->wdq_match != NULL; wdq++) {
+ /*
+ * We only want exact matches (which include matches
+ * against globbing characters).
+ */
+ if (pmatch(name, wdq->wdq_match, &estr) == 2)
+ return (wdq);
+ }
+ return (NULL);
+}
+
int
wdprobe(parent, match, aux)
struct device *parent;
@@ -233,6 +279,7 @@ wdattach(parent, self, aux)
struct ata_device *adev= aux;
int i, blank;
char buf[41], pbuf[9], c, *p, *q;
+ const struct wd_quirk *wdq;
WDCDEBUG_PRINT(("wdattach\n"), DEBUG_FUNCS | DEBUG_PROBE);
callout_init(&wd->sc_restart_ch);
@@ -273,6 +320,10 @@ wdattach(parent, self, aux)
printf(": <%s>\n", buf);
+ wdq = wd_lookup_quirks(buf);
+ if (wdq != NULL)
+ wd->sc_quirks = wdq->wdq_quirks;
+
if ((wd->sc_params.atap_multi & 0xff) > 1) {
wd->sc_multi = wd->sc_params.atap_multi & 0xff;
} else {
@@ -511,12 +562,103 @@ wdstart(arg)
}
}
+static void
+wd_split_mod15_write(struct buf *bp)
+{
+ struct buf *obp = bp->b_private;
+ struct wd_softc *sc = wd_cd.cd_devs[DISKUNIT(obp->b_dev)];
+
+ if (__predict_false(bp->b_flags & B_ERROR) != 0) {
+ /*
+ * Propagate the error. If this was the first half of
+ * the original transfer, make sure to account for that
+ * in the residual.
+ */
+ if (bp->b_data == obp->b_data)
+ bp->b_resid += bp->b_bcount;
+ goto done;
+ }
+
+ /*
+ * If this was the second half of the transfer, we're all done!
+ */
+ if (bp->b_data != obp->b_data)
+ goto done;
+
+ /*
+ * Advance the pointer to the second half and issue that command
+ * using the same opening.
+ */
+ bp->b_flags = obp->b_flags | B_CALL;
+ bp->b_data += bp->b_bcount;
+ bp->b_blkno += (bp->b_bcount / 512);
+ bp->b_rawblkno += (bp->b_bcount / 512);
+ __wdstart(sc, bp);
+ return;
+
+ done:
+ obp->b_flags |= (bp->b_flags & (B_EINTR|B_ERROR));
+ obp->b_error = bp->b_error;
+ obp->b_resid = bp->b_resid;
+ pool_put(&bufpool, bp);
+ biodone(obp);
+ sc->openings++;
+ /* wddone() will call wdstart() */
+}
+
void
__wdstart(wd, bp)
struct wd_softc *wd;
struct buf *bp;
{
+ /*
+ * Deal with the "split mod15 write" quirk. We just divide the
+ * transfer in two, doing the first half and then then second half
+ * with the same command opening.
+ *
+ * Note we MUST do this here, because we can't let insertion
+ * into the bufq cause the transfers to be re-merged.
+ */
+ if (__predict_false((wd->sc_quirks & WD_QUIRK_SPLIT_MOD15_WRITE) != 0 &&
+ (bp->b_flags & B_READ) == 0 &&
+ bp->b_bcount > 512 &&
+ ((bp->b_bcount / 512) % 15) == 1)) {
+ struct buf *nbp;
+
+ /* already at splbio */
+ nbp = pool_get(&bufpool, PR_NOWAIT);
+ if (__predict_false(nbp == NULL)) {
+ /* No memory -- fail the iop. */
+ bp->b_error = ENOMEM;
+ bp->b_flags |= B_ERROR;
+ bp->b_resid = bp->b_bcount;
+ biodone(bp);
+ wd->openings++;
+ return;
+ }
+
+ BUF_INIT(nbp);
+ nbp->b_error = 0;
+ nbp->b_proc = bp->b_proc;
+ nbp->b_vp = NULLVP;
+ nbp->b_dev = bp->b_dev;
+
+ nbp->b_bcount = bp->b_bcount / 2;
+ nbp->b_bufsize = bp->b_bcount / 2;
+ nbp->b_data = bp->b_data;
+
+ nbp->b_blkno = bp->b_blkno;
+ nbp->b_rawblkno = bp->b_rawblkno;
+
+ nbp->b_flags = bp->b_flags | B_CALL;
+ nbp->b_iodone = wd_split_mod15_write;
+
+ /* Put ptr to orig buf in b_private and use new buf */
+ nbp->b_private = bp;
+ bp = nbp;
+ }
+
wd->sc_wdc_bio.blkno = bp->b_rawblkno;
wd->sc_wdc_bio.blkdone =0;
wd->sc_bp = bp;
@@ -615,8 +757,14 @@ noerror: if ((wd->sc_wdc_bio.flags & ATA_CORR) || wd->retries > 0)
#if NRND > 0
rnd_add_uint32(&wd->rnd_source, bp->b_blkno);
#endif
- biodone(bp);
- wd->openings++;
+ /* XXX Yuck, but we don't want to increment openings in this case */
+ if (__predict_false((bp->b_flags & B_CALL) != 0 &&
+ bp->b_iodone == wd_split_mod15_write))
+ biodone(bp);
+ else {
+ biodone(bp);
+ wd->openings++;
+ }
wdstart(wd);
}
diff --git a/sys/dev/ata/wdvar.h b/sys/dev/ata/wdvar.h
index 0c8e74ab9dd..dbca69ea8c3 100644
--- a/sys/dev/ata/wdvar.h
+++ b/sys/dev/ata/wdvar.h
@@ -1,4 +1,4 @@
-/* $NetBSD: wdvar.h,v 1.13 2003/01/27 18:21:30 thorpej Exp $ */
+/* $NetBSD: wdvar.h,v 1.14 2003/03/21 22:40:57 thorpej Exp $ */
/*
* Copyright (c) 1998, 2001 Manuel Bouyer.
@@ -105,6 +105,7 @@ struct wd_softc {
struct disk sc_dk;
struct bufq_state sc_q;
struct callout sc_restart_ch;
+ int sc_quirks; /* any quirks drive might have */
/* IDE disk soft states */
struct ata_bio sc_wdc_bio; /* current transfer */
struct buf *sc_bp; /* buf being transfered */