diff options
| author | bouyer <bouyer@NetBSD.org> | 2004-07-31 21:26:42 +0000 |
|---|---|---|
| committer | bouyer <bouyer@NetBSD.org> | 2004-07-31 21:26:42 +0000 |
| commit | 7ffa35d1a22116956594de8f808c7abf31912a98 (patch) | |
| tree | 285bb74d8dd06591e5f2ad168be1a18315fca8d4 /sys/dev | |
| parent | 1fe630831fca7a6764e893910c062a2930cfb546 (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')
| -rw-r--r-- | sys/dev/ata/ata_wdc.c | 23 | ||||
| -rw-r--r-- | sys/dev/ata/atavar.h | 18 | ||||
| -rw-r--r-- | sys/dev/ata/wd.c | 9 | ||||
| -rw-r--r-- | sys/dev/ic/wdc.c | 101 | ||||
| -rw-r--r-- | sys/dev/scsipi/atapi_wdc.c | 25 |
5 files changed, 139 insertions, 37 deletions
diff --git a/sys/dev/ata/ata_wdc.c b/sys/dev/ata/ata_wdc.c index 4a6bc577bff..02da34e06e5 100644 --- a/sys/dev/ata/ata_wdc.c +++ b/sys/dev/ata/ata_wdc.c @@ -1,4 +1,4 @@ -/* $NetBSD: ata_wdc.c,v 1.57 2004/06/22 19:20:56 mycroft Exp $ */ +/* $NetBSD: ata_wdc.c,v 1.58 2004/07/31 21:26:43 bouyer Exp $ */ /* * Copyright (c) 1998, 2001, 2003 Manuel Bouyer. @@ -66,7 +66,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ata_wdc.c,v 1.57 2004/06/22 19:20:56 mycroft Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ata_wdc.c,v 1.58 2004/07/31 21:26:43 bouyer Exp $"); #ifndef WDCDEBUG #define WDCDEBUG @@ -119,7 +119,8 @@ static void wdc_ata_bio_start(struct wdc_channel *,struct ata_xfer *); static void _wdc_ata_bio_start(struct wdc_channel *,struct ata_xfer *); static int wdc_ata_bio_intr(struct wdc_channel *, struct ata_xfer *, int); -static void wdc_ata_bio_kill_xfer(struct wdc_channel *,struct ata_xfer *); +static void wdc_ata_bio_kill_xfer(struct wdc_channel *, + struct ata_xfer *, int); static void wdc_ata_bio_done(struct wdc_channel *, struct ata_xfer *); static int wdc_ata_err(struct ata_drive_datas *, struct ata_bio *); #define WDC_ATA_NOERR 0x00 /* Drive doesn't report an error */ @@ -740,7 +741,8 @@ wdc_ata_kill_pending(struct ata_drive_datas *drvp) } static void -wdc_ata_bio_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer) +wdc_ata_bio_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer, + int reason) { struct ata_bio *ata_bio = xfer->c_cmd; int drive = xfer->c_drive; @@ -750,7 +752,18 @@ wdc_ata_bio_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer) wdc_free_xfer(chp, xfer); ata_bio->flags |= ATA_ITSDONE; - ata_bio->error = ERR_NODEV; + switch (reason) { + case KILL_GONE: + ata_bio->error = ERR_NODEV; + break; + case KILL_RESET: + ata_bio->error = ERR_RESET; + break; + default: + printf("wdc_ata_bio_kill_xfer: unknown reason %d\n", + reason); + panic("wdc_ata_bio_kill_xfer"); + } ata_bio->r_error = WDCE_ABRT; WDCDEBUG_PRINT(("wdc_ata_done: drv_done\n"), DEBUG_XFERS); (*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc); diff --git a/sys/dev/ata/atavar.h b/sys/dev/ata/atavar.h index ac922ca7865..0844268b652 100644 --- a/sys/dev/ata/atavar.h +++ b/sys/dev/ata/atavar.h @@ -1,4 +1,4 @@ -/* $NetBSD: atavar.h,v 1.43 2004/06/01 19:32:30 mycroft Exp $ */ +/* $NetBSD: atavar.h,v 1.44 2004/07/31 21:26:43 bouyer Exp $ */ /* * Copyright (c) 1998, 2001 Manuel Bouyer. @@ -58,14 +58,19 @@ struct ata_xfer { /* Low-level protocol handlers. */ void (*c_start)(struct wdc_channel *, struct ata_xfer *); int (*c_intr)(struct wdc_channel *, struct ata_xfer *, int); - void (*c_kill_xfer)(struct wdc_channel *, struct ata_xfer *); + void (*c_kill_xfer)(struct wdc_channel *, struct ata_xfer *, int); }; +/* vlags in c_flags */ #define C_ATAPI 0x0001 /* xfer is ATAPI request */ #define C_TIMEOU 0x0002 /* xfer processing timed out */ #define C_POLL 0x0004 /* command is polled */ #define C_DMA 0x0008 /* command uses DMA */ +/* reasons for c_kill_xfer() */ +#define KILL_GONE 1 /* device is gone */ +#define KILL_RESET 2 /* xfer was reset */ + /* Per-channel queue of ata_xfers. May be shared by multiple channels. */ struct ata_queue { TAILQ_HEAD(, ata_xfer) queue_xfer; @@ -192,6 +197,7 @@ struct ata_bio { #define ERR_DMA 3 /* DMA error */ #define TIMEOUT 4 /* device timed out */ #define ERR_NODEV 5 /* device has been gone */ +#define ERR_RESET 6 /* command was terminated by channel reset */ u_int8_t r_error;/* copy of error register */ daddr_t badsect[127];/* 126 plus trailing -1 marker */ }; @@ -232,7 +238,9 @@ struct wdc_command { #define AT_ERROR 0x0080 /* command is done with error */ #define AT_TIMEOU 0x0100 /* command timed out */ #define AT_DF 0x0200 /* Drive fault */ -#define AT_READREG 0x0400 /* Read registers on completion */ +#define AT_RESET 0x0400 /* command terminated by channel reset */ +#define AT_GONE 0x0800 /* command terminated because device is gone */ +#define AT_READREG 0x1000 /* Read registers on completion */ int timeout; /* timeout (in ms) */ void *data; /* Data buffer address */ @@ -249,6 +257,10 @@ struct ata_bustype { int bustype_type; /* symbolic name of type */ int (*ata_bio)(struct ata_drive_datas *, struct ata_bio *); void (*ata_reset_channel)(struct ata_drive_datas *, int); +/* extra flags for ata_reset_channel(), in addition to AT_* */ +#define AT_RST_EMERG 0x10000 /* emergency - e.g. for a dump */ +#define AT_RST_NOCMD 0x20000 /* XXX has to go - temporary until we have tagged queuing */ + int (*ata_exec_command)(struct ata_drive_datas *, struct wdc_command *); diff --git a/sys/dev/ata/wd.c b/sys/dev/ata/wd.c index 7198f2ff256..a949e820e69 100644 --- a/sys/dev/ata/wd.c +++ b/sys/dev/ata/wd.c @@ -1,4 +1,4 @@ -/* $NetBSD: wd.c,v 1.280 2004/06/22 19:20:56 mycroft Exp $ */ +/* $NetBSD: wd.c,v 1.281 2004/07/31 21:26:43 bouyer 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.280 2004/06/22 19:20:56 mycroft Exp $"); +__KERNEL_RCSID(0, "$NetBSD: wd.c,v 1.281 2004/07/31 21:26:43 bouyer Exp $"); #ifndef WDCDEBUG #define WDCDEBUG @@ -742,6 +742,9 @@ wddone(void *v) case TIMEOUT: errmsg = "device timeout"; goto retry; + case ERR_RESET: + errmsg = "channel reset"; + goto retry2; case ERROR: /* Don't care about media change bits */ if (wd->sc_wdc_bio.r_error != 0 && @@ -751,6 +754,7 @@ wddone(void *v) do_perror = 1; retry: /* Just reset and retry. Can we do more ? */ wd->atabus->ata_reset_channel(wd->drvp, 0); +retry2: diskerr(bp, "wd", errmsg, LOG_PRINTF, wd->sc_wdc_bio.blkdone, wd->sc_dk.dk_label); if (wd->retries < WDIORETRIES) @@ -1449,6 +1453,7 @@ wddump(dev_t dev, daddr_t blkno, caddr_t va, size_t size) if (wddumprecalibrated == 0) { wddumpmulti = wd->sc_multi; wddumprecalibrated = 1; + wd->atabus->ata_reset_channel(wd->drvp, AT_POLL | AT_RST_EMERG); wd->drvp->state = RESET; } 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); } } diff --git a/sys/dev/scsipi/atapi_wdc.c b/sys/dev/scsipi/atapi_wdc.c index b2a70f5f707..c3a5b8512b8 100644 --- a/sys/dev/scsipi/atapi_wdc.c +++ b/sys/dev/scsipi/atapi_wdc.c @@ -1,4 +1,4 @@ -/* $NetBSD: atapi_wdc.c,v 1.70 2004/05/08 15:03:32 bouyer Exp $ */ +/* $NetBSD: atapi_wdc.c,v 1.71 2004/07/31 21:26:43 bouyer Exp $ */ /* * Copyright (c) 1998, 2001 Manuel Bouyer. @@ -30,7 +30,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: atapi_wdc.c,v 1.70 2004/05/08 15:03:32 bouyer Exp $"); +__KERNEL_RCSID(0, "$NetBSD: atapi_wdc.c,v 1.71 2004/07/31 21:26:43 bouyer Exp $"); #ifndef WDCDEBUG #define WDCDEBUG @@ -88,7 +88,8 @@ static void wdc_atapi_probe_device(struct atapibus_softc *, int); static void wdc_atapi_minphys (struct buf *bp); static void wdc_atapi_start(struct wdc_channel *,struct ata_xfer *); static int wdc_atapi_intr(struct wdc_channel *, struct ata_xfer *, int); -static void wdc_atapi_kill_xfer(struct wdc_channel *, struct ata_xfer *); +static void wdc_atapi_kill_xfer(struct wdc_channel *, + struct ata_xfer *, int); static void wdc_atapi_phase_complete(struct ata_xfer *); static void wdc_atapi_done(struct wdc_channel *, struct ata_xfer *); static void wdc_atapi_reset(struct wdc_channel *, struct ata_xfer *); @@ -168,15 +169,27 @@ wdc_atapi_kill_pending(struct scsipi_periph *periph) } static void -wdc_atapi_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer) +wdc_atapi_kill_xfer(struct wdc_channel *chp, struct ata_xfer *xfer, int reason) { struct scsipi_xfer *sc_xfer = xfer->c_cmd; callout_stop(&chp->ch_callout); /* remove this command from xfer queue */ wdc_free_xfer(chp, xfer); - sc_xfer->error = XS_DRIVER_STUFFUP; - scsipi_done(sc_xfer); + switch (reason) { + case KILL_GONE: + sc_xfer->error = XS_DRIVER_STUFFUP; + scsipi_done(sc_xfer); + break; + case KILL_RESET: + sc_xfer->error = XS_RESET; + wdc_atapi_reset(chp, xfer); + break; + default: + printf("wdc_ata_bio_kill_xfer: unknown reason %d\n", + reason); + panic("wdc_ata_bio_kill_xfer"); + } } static int |
