summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheo de Raadt <deraadt@cvs.openbsd.org>2011-04-18 04:16:15 +0000
committerTheo de Raadt <deraadt@cvs.openbsd.org>2011-04-18 04:16:15 +0000
commit1dd9da86643ecc787c10e8c2dcc955a035743fbd (patch)
tree621d7bd90084758b4f455ae797cc0dee88b941b2
parent8630210480621a9c87eb7f3a8360ba1228c2a4f7 (diff)
keep an eye out for wdc registers returning 0xff. if they return 0xff,
this probably means the controller is dead -- as in, a hot plug eject event. in that case, mark the driver dead and try harder to return -1 in the interrupt handler. tested by many ok dlg, miod did not object
-rw-r--r--sys/dev/ata/ata_wdc.c9
-rw-r--r--sys/dev/ata/wd.c13
-rw-r--r--sys/dev/ic/wdc.c66
-rw-r--r--sys/dev/ic/wdcvar.h4
-rw-r--r--sys/dev/pci/pciide.c4
5 files changed, 74 insertions, 22 deletions
diff --git a/sys/dev/ata/ata_wdc.c b/sys/dev/ata/ata_wdc.c
index 3840f43ed39..41d9a21fddc 100644
--- a/sys/dev/ata/ata_wdc.c
+++ b/sys/dev/ata/ata_wdc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ata_wdc.c,v 1.37 2011/04/05 19:57:40 deraadt Exp $ */
+/* $OpenBSD: ata_wdc.c,v 1.38 2011/04/18 04:16:13 deraadt Exp $ */
/* $NetBSD: ata_wdc.c,v 1.21 1999/08/09 09:43:11 bouyer Exp $ */
/*
@@ -420,6 +420,8 @@ intr: /* Wait for IRQ (either real or polled) */
}
return;
timeout:
+ if (chp->ch_status == 0xff)
+ return;
printf("%s:%d:%d: not ready, st=0x%b, err=0x%02x\n",
chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
chp->ch_status, WDCS_BITS, chp->ch_error);
@@ -756,6 +758,11 @@ wdc_ata_err(struct ata_drive_datas *drvp, struct ata_bio *ata_bio)
{
struct channel_softc *chp = drvp->chnl_softc;
ata_bio->error = 0;
+
+ if (chp->ch_status == 0xff) {
+ ata_bio->error = ERR_NODEV;
+ return WDC_ATA_ERR;
+ }
if (chp->ch_status & WDCS_BSY) {
ata_bio->error = TIMEOUT;
return WDC_ATA_ERR;
diff --git a/sys/dev/ata/wd.c b/sys/dev/ata/wd.c
index eedfac0c256..bf473917e88 100644
--- a/sys/dev/ata/wd.c
+++ b/sys/dev/ata/wd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: wd.c,v 1.99 2011/04/15 20:53:28 miod Exp $ */
+/* $OpenBSD: wd.c,v 1.100 2011/04/18 04:16:13 deraadt Exp $ */
/* $NetBSD: wd.c,v 1.193 1999/02/28 17:15:27 explorer Exp $ */
/*
@@ -375,6 +375,8 @@ wddetach(struct device *self, int flags)
struct buf *bp;
int s, bmaj, cmaj, mn;
+ timeout_del(&sc->sc_restart_timeout);
+
/* Remove unprocessed buffers from queue */
s = splbio();
while ((bp = bufq_dequeue(&sc->sc_bufq)) != NULL) {
@@ -612,10 +614,15 @@ wdrestart(void *v)
{
struct wd_softc *wd = v;
struct buf *bp = wd->sc_bp;
+ struct channel_softc *chnl;
int s;
WDCDEBUG_PRINT(("wdrestart %s\n", wd->sc_dev.dv_xname),
DEBUG_XFERS);
+ chnl = (struct channel_softc *)(wd->drvp->chnl_softc);
+ if (chnl->dying)
+ return;
+
s = splbio();
disk_unbusy(&wd->sc_dk, 0, (bp->b_flags & B_READ));
__wdstart(v, bp);
@@ -642,6 +649,7 @@ int
wdopen(dev_t dev, int flag, int fmt, struct proc *p)
{
struct wd_softc *wd;
+ struct channel_softc *chnl;
int unit, part;
int error;
@@ -651,6 +659,9 @@ wdopen(dev_t dev, int flag, int fmt, struct proc *p)
wd = wdlookup(unit);
if (wd == NULL)
return ENXIO;
+ chnl = (struct channel_softc *)(wd->drvp->chnl_softc);
+ if (chnl->dying)
+ return (ENXIO);
/*
* If this is the first open of this device, add a reference
diff --git a/sys/dev/ic/wdc.c b/sys/dev/ic/wdc.c
index a43eb881501..2900586cd81 100644
--- a/sys/dev/ic/wdc.c
+++ b/sys/dev/ic/wdc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: wdc.c,v 1.112 2011/04/15 20:53:28 miod Exp $ */
+/* $OpenBSD: wdc.c,v 1.113 2011/04/18 04:16:13 deraadt Exp $ */
/* $NetBSD: wdc.c,v 1.68 1999/06/23 19:00:17 bouyer Exp $ */
/*
* Copyright (c) 1998, 2001 Manuel Bouyer. All rights reserved.
@@ -702,7 +702,7 @@ wdcattach(struct channel_softc *chp)
{
int channel_flags, ctrl_flags, i;
struct ata_atapi_attach aa_link;
- static int inited = 0;
+ static int inited = 0, s;
#ifdef WDCDEBUG
int savedmask = wdcdebug_mask;
#endif
@@ -743,6 +743,7 @@ wdcattach(struct channel_softc *chp)
#endif /* WDCDEBUG */
/* initialise global data */
+ s = splbio();
if (inited == 0) {
/* Initialize the wdc_xfer pool. */
pool_init(&wdc_xfer_pool, sizeof(struct wdc_xfer), 0,
@@ -750,6 +751,7 @@ wdcattach(struct channel_softc *chp)
inited++;
}
TAILQ_INIT(&chp->ch_queue->sc_xfer);
+ splx(s);
for (i = 0; i < 2; i++) {
struct ata_drive_datas *drvp = &chp->ch_drive[i];
@@ -879,7 +881,10 @@ wdcdetach(struct channel_softc *chp, int flags)
int s, rv;
s = splbio();
+ chp->dying = 1;
+
wdc_kill_pending(chp);
+ timeout_del(&chp->ch_timo);
rv = config_detach_children((struct device *)chp->wdc, flags);
splx(s);
@@ -898,19 +903,21 @@ wdcintr(void *arg)
{
struct channel_softc *chp = arg;
struct wdc_xfer *xfer;
- int ret;
+ u_int8_t st = 0;
+ int ret = 0;
- if ((chp->ch_flags & WDCF_IRQ_WAIT) == 0) {
- /* Acknowledge interrupt by reading status */
- if (chp->_vtbl == 0) {
- bus_space_read_1(chp->cmd_iot, chp->cmd_ioh,
- wdr_status & _WDC_REGMASK);
- } else {
- CHP_READ_REG(chp, wdr_status);
- }
+ /* Acknowledge interrupt by reading status */
+ if (chp->_vtbl == 0)
+ st = bus_space_read_1(chp->cmd_iot, chp->cmd_ioh,
+ wdr_status & _WDC_REGMASK);
+ else
+ st = CHP_READ_REG(chp, wdr_status);
+ if (st == 0xff)
+ return (-1);
+ if ((chp->ch_flags & WDCF_IRQ_WAIT) == 0) {
WDCDEBUG_PRINT(("wdcintr: inactive controller\n"), DEBUG_INTR);
- return 0;
+ return ret;
}
WDCDEBUG_PRINT(("wdcintr\n"), DEBUG_INTR);
@@ -919,12 +926,15 @@ wdcintr(void *arg)
chp->wdc->dma_status =
(*chp->wdc->dma_finish)(chp->wdc->dma_arg, chp->channel,
xfer->drive, 0);
+ if (chp->wdc->dma_status == 0xff)
+ return (-1);
if (chp->wdc->dma_status & WDC_DMAST_NOIRQ) {
/* IRQ not for us, not detected by DMA engine */
return 0;
}
chp->ch_flags &= ~WDCF_DMA_WAIT;
}
+
chp->ch_flags &= ~WDCF_IRQ_WAIT;
ret = xfer->c_intr(chp, xfer, 1);
if (ret == 0) /* irq was not for us, still waiting for irq */
@@ -1061,11 +1071,16 @@ wdc_wait_for_status(struct channel_softc *chp, int mask, int bits, int timeout)
chp->ch_status = status = CHP_READ_REG(chp, wdr_status);
WDC_LOG_STATUS(chp, chp->ch_status);
- if (status == 0xff && (chp->ch_flags & WDCF_ONESLAVE)) {
- wdc_set_drive(chp, 1);
- chp->ch_status = status =
- CHP_READ_REG(chp, wdr_status);
- WDC_LOG_STATUS(chp, chp->ch_status);
+ if (status == 0xff) {
+ if ((chp->ch_flags & WDCF_ONESLAVE)) {
+ wdc_set_drive(chp, 1);
+ chp->ch_status = status =
+ CHP_READ_REG(chp, wdr_status);
+ WDC_LOG_STATUS(chp, chp->ch_status);
+ } else {
+ chp->dying = 1;
+ return -1;
+ }
}
if ((status & WDCS_BSY) == 0 && (status & mask) == bits)
break;
@@ -1117,6 +1132,10 @@ wdc_dmawait(struct channel_softc *chp, struct wdc_xfer *xfer, int timeout)
chp->channel, xfer->drive, 0);
if ((chp->wdc->dma_status & WDC_DMAST_NOIRQ) == 0)
return 0;
+ if (chp->wdc->dma_status == 0xff) {
+ chp->dying = 1;
+ return -1;
+ }
delay(WDCDELAY);
}
/* timeout, force a DMA halt */
@@ -1700,6 +1719,10 @@ __wdccommand_intr(struct channel_softc *chp, struct wdc_xfer *xfer, int irq)
chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive), DEBUG_INTR);
if (wdcwait(chp, wdc_c->r_st_pmask, wdc_c->r_st_pmask,
(irq == 0) ? wdc_c->timeout : 0)) {
+ if (chp->dying) {
+ __wdccommand_done(chp, xfer);
+ return -1;
+ }
if (irq && (xfer->c_flags & C_TIMEOU) == 0)
return 0; /* IRQ was not for us */
wdc_c->flags |= AT_TIMEOU;
@@ -1729,6 +1752,8 @@ __wdccommand_done(struct channel_softc *chp, struct wdc_xfer *xfer)
WDCDEBUG_PRINT(("__wdccommand_done %s:%d:%d %02x\n",
chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive,
chp->ch_status), DEBUG_FUNCS);
+ if (chp->dying)
+ goto killit;
if (chp->ch_status & WDCS_DWF)
wdc_c->flags |= AT_DF;
if (chp->ch_status & WDCS_ERR) {
@@ -1749,13 +1774,18 @@ __wdccommand_done(struct channel_softc *chp, struct wdc_xfer *xfer)
isn't a readable register */
}
+killit:
if (xfer->c_flags & C_POLL) {
wdc_enable_intr(chp);
- }
+ } else
+ timeout_del(&chp->ch_timo);
wdc_free_xfer(chp, xfer);
WDCDEBUG_PRINT(("__wdccommand_done before callback\n"), DEBUG_INTR);
+ if (chp->dying)
+ return;
+
if (wdc_c->flags & AT_WAIT)
wakeup(wdc_c);
else
diff --git a/sys/dev/ic/wdcvar.h b/sys/dev/ic/wdcvar.h
index 7e67e635ec7..80247362390 100644
--- a/sys/dev/ic/wdcvar.h
+++ b/sys/dev/ic/wdcvar.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: wdcvar.h,v 1.48 2011/04/15 20:53:28 miod Exp $ */
+/* $OpenBSD: wdcvar.h,v 1.49 2011/04/18 04:16:14 deraadt Exp $ */
/* $NetBSD: wdcvar.h,v 1.17 1999/04/11 20:50:29 bouyer Exp $ */
/*-
@@ -81,6 +81,8 @@ struct channel_softc { /* Per channel data */
*/
struct channel_queue *ch_queue;
struct timeout ch_timo;
+
+ int dying;
};
/*
diff --git a/sys/dev/pci/pciide.c b/sys/dev/pci/pciide.c
index a53c91d76e3..3f75e73a852 100644
--- a/sys/dev/pci/pciide.c
+++ b/sys/dev/pci/pciide.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: pciide.c,v 1.326 2011/04/14 22:24:34 jsg Exp $ */
+/* $OpenBSD: pciide.c,v 1.327 2011/04/18 04:16:12 deraadt Exp $ */
/* $NetBSD: pciide.c,v 1.127 2001/08/03 01:31:08 tsutsui Exp $ */
/*
@@ -2073,6 +2073,8 @@ pciide_dma_finish(void *v, int channel, int drive, int force)
status = PCIIDE_DMACTL_READ(sc, channel);
WDCDEBUG_PRINT(("pciide_dma_finish: status 0x%x\n", status),
DEBUG_XFERS);
+ if (status == 0xff)
+ return (status);
if (force == 0 && (status & IDEDMA_CTL_INTR) == 0) {
error = WDC_DMAST_NOIRQ;