summaryrefslogtreecommitdiff
path: root/sys/dev/ic/wdc.c
diff options
context:
space:
mode:
authorbouyer <bouyer@NetBSD.org>2004-07-31 21:26:42 +0000
committerbouyer <bouyer@NetBSD.org>2004-07-31 21:26:42 +0000
commit7ffa35d1a22116956594de8f808c7abf31912a98 (patch)
tree285bb74d8dd06591e5f2ad168be1a18315fca8d4 /sys/dev/ic/wdc.c
parent1fe630831fca7a6764e893910c062a2930cfb546 (diff)
Implement asynchronous channel reset.
Use this to reset the channel before doing a dump, instead of the hack in wdc_exec_xfer() based on C_POLL. This hack was causing problems on controllers with a shared queue, because we now can have C_POLL set during concurent channels probes (problem found and analysed on sparc64 by Martin Husemann). This should even make core dumps marginally more reliable on ATA drives.
Diffstat (limited to 'sys/dev/ic/wdc.c')
-rw-r--r--sys/dev/ic/wdc.c101
1 files changed, 80 insertions, 21 deletions
diff --git a/sys/dev/ic/wdc.c b/sys/dev/ic/wdc.c
index d4989bf4ce3..57acd6b010d 100644
--- a/sys/dev/ic/wdc.c
+++ b/sys/dev/ic/wdc.c
@@ -1,4 +1,4 @@
-/* $NetBSD: wdc.c,v 1.181 2004/06/23 21:10:52 bouyer Exp $ */
+/* $NetBSD: wdc.c,v 1.182 2004/07/31 21:26:42 bouyer Exp $ */
/*
* Copyright (c) 1998, 2001, 2003 Manuel Bouyer. All rights reserved.
@@ -70,7 +70,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: wdc.c,v 1.181 2004/06/23 21:10:52 bouyer Exp $");
+__KERNEL_RCSID(0, "$NetBSD: wdc.c,v 1.182 2004/07/31 21:26:42 bouyer Exp $");
#ifndef WDCDEBUG
#define WDCDEBUG
@@ -146,10 +146,13 @@ const struct ata_bustype wdc_ata_bustype = {
static int wdcprobe1(struct wdc_channel*, int);
static void __wdcerror(struct wdc_channel*, char *);
static int __wdcwait_reset(struct wdc_channel *, int, int);
+static void __wdc_reset_channel(struct wdc_channel *, int);
static void __wdccommand_done(struct wdc_channel *, struct ata_xfer *);
+static void __wdccommand_done_end(struct wdc_channel *, struct ata_xfer *);
+static void __wdccommand_kill_xfer(struct wdc_channel *,
+ struct ata_xfer *, int);
static void __wdccommand_start(struct wdc_channel *, struct ata_xfer *);
-static int __wdccommand_intr(struct wdc_channel *, struct ata_xfer *,
- int);
+static int __wdccommand_intr(struct wdc_channel *, struct ata_xfer *, int);
static int __wdcwait(struct wdc_channel *, int, int, int);
#define DEBUG_INTR 0x01
@@ -966,18 +969,46 @@ wdc_reset_channel(struct ata_drive_datas *drvp, int flags)
{
struct wdc_channel *chp = drvp->chnl_softc;
struct wdc_softc *wdc = chp->ch_wdc;
- int drive;
-
WDCDEBUG_PRINT(("ata_reset_channel %s:%d for drive %d\n",
wdc->sc_dev.dv_xname, chp->ch_channel, drvp->drive),
DEBUG_FUNCS);
+
+
+ __wdc_reset_channel(chp, flags);
+}
+
+static void
+__wdc_reset_channel(struct wdc_channel *chp, int flags)
+{
+ struct ata_xfer *xfer;
+ int drive;
+
+ /*
+ * look for pending xfers. If we have a shared queue, we'll also reset
+ * the other channel if the current xfer is running on it.
+ * Then we'll freese the queue, and dequeue only the xfers for this
+ * channel. xfer->c_kill_xfer() will reset any ATAPI device when
+ * needed.
+ */
+ chp->ch_queue->queue_freeze++;
+ if ((flags & AT_RST_NOCMD) == 0) {
+ xfer = TAILQ_FIRST(&chp->ch_queue->queue_xfer);
+ if (xfer && xfer->c_chp != chp)
+ __wdc_reset_channel(xfer->c_chp, flags);
+ for (xfer = TAILQ_FIRST(&chp->ch_queue->queue_xfer);
+ xfer != 0; ) {
+ if (xfer->c_chp != chp)
+ continue;
+ if ((flags & AT_RST_EMERG) == 0)
+ xfer->c_kill_xfer(chp, xfer, KILL_RESET);
+ }
+ }
if ((flags & AT_POLL) == 0) {
if (chp->ch_flags & WDCF_TH_RESET) {
/* no need to schedule a reset more than one time */
return;
}
chp->ch_flags |= WDCF_TH_RESET;
- chp->ch_queue->queue_freeze++;
wakeup(&chp->ch_thread);
return;
}
@@ -985,6 +1016,14 @@ wdc_reset_channel(struct ata_drive_datas *drvp, int flags)
for (drive = 0; drive < 2; drive++) {
chp->ch_drive[drive].state = 0;
}
+ if ((flags & AT_RST_EMERG) == 0) {
+ chp->ch_queue->queue_freeze--;
+ wdcstart(chp);
+ } else {
+ /* make sure that we can use polled commands */
+ TAILQ_INIT(&chp->ch_queue->queue_xfer);
+ chp->ch_queue->queue_freeze = 0;
+ }
}
int
@@ -1565,7 +1604,7 @@ wdc_downgrade_mode(struct ata_drive_datas *drvp, int flags)
wdc->set_modes(chp);
wdc_print_modes(chp);
/* reset the channel, which will shedule all drives for setup */
- wdc_reset_channel(drvp, flags);
+ wdc_reset_channel(drvp, flags | AT_RST_NOCMD);
return 1;
}
@@ -1598,7 +1637,7 @@ wdc_exec_command(struct ata_drive_datas *drvp, struct wdc_command *wdc_c)
xfer->c_cmd = wdc_c;
xfer->c_start = __wdccommand_start;
xfer->c_intr = __wdccommand_intr;
- xfer->c_kill_xfer = __wdccommand_done;
+ xfer->c_kill_xfer = __wdccommand_kill_xfer;
s = splbio();
wdc_exec_xfer(chp, xfer);
@@ -1780,7 +1819,6 @@ __wdccommand_done(struct wdc_channel *chp, struct ata_xfer *xfer)
wdc->sc_dev.dv_xname, chp->ch_channel, xfer->c_drive),
DEBUG_FUNCS);
- callout_stop(&chp->ch_callout);
if (chp->ch_status & WDCS_DWF)
wdc_c->flags |= AT_DF;
@@ -1788,7 +1826,6 @@ __wdccommand_done(struct wdc_channel *chp, struct ata_xfer *xfer)
wdc_c->flags |= AT_ERROR;
wdc_c->r_error = chp->ch_error;
}
- wdc_c->flags |= AT_DONE;
if ((wdc_c->flags & AT_READREG) != 0 &&
(wdc->sc_dev.dv_flags & DVF_ACTIVE) != 0 &&
(wdc_c->flags & (AT_ERROR | AT_DF)) == 0) {
@@ -1807,7 +1844,16 @@ __wdccommand_done(struct wdc_channel *chp, struct ata_xfer *xfer)
wdc_c->r_features = bus_space_read_1(chp->cmd_iot,
chp->cmd_iohs[wd_features], 0);
}
+ __wdccommand_done_end(chp, xfer);
+}
+static void
+__wdccommand_done_end(struct wdc_channel *chp, struct ata_xfer *xfer)
+{
+ struct wdc_command *wdc_c = xfer->c_cmd;
+
+ callout_stop(&chp->ch_callout);
+ wdc_c->flags |= AT_DONE;
if (wdc_c->flags & AT_POLL) {
/* enable interrupts */
bus_space_write_1(chp->ctl_iot, chp->ctl_ioh, wd_aux_ctlr,
@@ -1823,6 +1869,28 @@ __wdccommand_done(struct wdc_channel *chp, struct ata_xfer *xfer)
return;
}
+static void
+__wdccommand_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer,
+ int reason)
+{
+ struct wdc_command *wdc_c = xfer->c_cmd;
+
+ switch (reason) {
+ case KILL_GONE:
+ wdc_c->flags |= AT_GONE;
+ break;
+ case KILL_RESET:
+ wdc_c->flags |= AT_RESET;
+ break;
+ default:
+ printf("__wdccommand_kill_xfer: unknown reason %d\n",
+ reason);
+ panic("__wdccommand_kill_xfer");
+ }
+ __wdccommand_done_end(chp, xfer);
+
+}
+
/*
* Send a command. The drive should be ready.
* Assumes interrupts are blocked.
@@ -1940,15 +2008,6 @@ wdc_exec_xfer(struct wdc_channel *chp, struct ata_xfer *xfer)
/* complete xfer setup */
xfer->c_chp = chp;
- /*
- * If we are a polled command, and the list is not empty,
- * we are doing a dump. Drop the list to allow the polled command
- * to complete, we're going to reboot soon anyway.
- */
- if ((xfer->c_flags & C_POLL) != 0 &&
- TAILQ_FIRST(&chp->ch_queue->queue_xfer) != NULL) {
- TAILQ_INIT(&chp->ch_queue->queue_xfer);
- }
/* insert at the end of command list */
TAILQ_INSERT_TAIL(&chp->ch_queue->queue_xfer, xfer, c_xferchain);
WDCDEBUG_PRINT(("wdcstart from wdc_exec_xfer, flags 0x%x\n",
@@ -1999,7 +2058,7 @@ wdc_kill_pending(struct wdc_channel *chp)
while ((xfer = TAILQ_FIRST(&chp->ch_queue->queue_xfer)) != NULL) {
chp = xfer->c_chp;
- (*xfer->c_kill_xfer)(chp, xfer);
+ (*xfer->c_kill_xfer)(chp, xfer, KILL_GONE);
}
}