- When starting an ATA or ATAPI transfer, handle the case where (*dma_init)()

returns EINVAL, indicating that DMA cannot be done for this transfer.
  Fall back to PIO in this case.
- Add a geodeide_dma_init() routine that checks to make sure that transfers
  start on a 16 byte boundary, returning EINVAL if not.  Works around a chip
  bug that causes a hard system hang.

Problem reported and patch tested by Erik Fair.
This commit is contained in:
thorpej 2005-07-06 01:46:52 +00:00
parent 053b017df8
commit 527d62e0a2
3 changed files with 58 additions and 14 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: ata_wdc.c,v 1.81 2005/06/07 13:45:11 peter Exp $ */
/* $NetBSD: ata_wdc.c,v 1.82 2005/07/06 01:46:52 thorpej 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.81 2005/06/07 13:45:11 peter Exp $");
__KERNEL_RCSID(0, "$NetBSD: ata_wdc.c,v 1.82 2005/07/06 01:46:52 thorpej Exp $");
#ifndef ATADEBUG
#define ATADEBUG
@ -323,7 +323,7 @@ _wdc_ata_bio_start(struct ata_channel *chp, struct ata_xfer *xfer)
int wait_flags = (xfer->c_flags & C_POLL) ? AT_POLL : 0;
u_int16_t cyl;
u_int8_t head, sect, cmd = 0;
int nblks;
int nblks, error;
int dma_flags = 0;
ATADEBUG_PRINT(("_wdc_ata_bio_start %s:%d:%d\n",
@ -397,10 +397,21 @@ again:
cmd = (ata_bio->flags & ATA_READ) ?
WDCC_READDMA : WDCC_WRITEDMA;
/* Init the DMA channel. */
if ((*wdc->dma_init)(wdc->dma_arg,
error = (*wdc->dma_init)(wdc->dma_arg,
chp->ch_channel, xfer->c_drive,
(char *)xfer->c_databuf + xfer->c_skip,
ata_bio->nbytes, dma_flags) != 0) {
ata_bio->nbytes, dma_flags);
if (error) {
if (error == EINVAL) {
/*
* We can't do DMA on this transfer
* for some reason. Fall back to
* PIO.
*/
xfer->c_flags &= ~C_DMA;
error = 0;
goto do_pio;
}
ata_bio->error = ERR_DMA;
ata_bio->r_error = 0;
wdc_ata_bio_done(chp, xfer);
@ -437,6 +448,7 @@ again:
/* wait for irq */
goto intr;
} /* else not DMA */
do_pio:
ata_bio->nblks = min(nblks, ata_bio->multi);
ata_bio->nbytes = ata_bio->nblks * ata_bio->lp->d_secsize;
KASSERT(nblks == 1 || (ata_bio->flags & ATA_SINGLE) == 0);

View File

@ -1,4 +1,4 @@
/* $NetBSD: geodeide.c,v 1.9 2005/06/25 05:04:01 fair Exp $ */
/* $NetBSD: geodeide.c,v 1.10 2005/07/06 01:46:52 thorpej Exp $ */
/*
* Copyright (c) 2004 Manuel Bouyer.
@ -37,7 +37,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: geodeide.c,v 1.9 2005/06/25 05:04:01 fair Exp $");
__KERNEL_RCSID(0, "$NetBSD: geodeide.c,v 1.10 2005/07/06 01:46:52 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -54,6 +54,7 @@ __KERNEL_RCSID(0, "$NetBSD: geodeide.c,v 1.9 2005/06/25 05:04:01 fair Exp $");
static void geodeide_chip_map(struct pciide_softc *,
struct pci_attach_args *);
static void geodeide_setup_channel(struct ata_channel *);
static int geodeide_dma_init(void *, int, int, void *, size_t, int);
static int geodeide_match(struct device *, struct cfdata *, void *);
static void geodeide_attach(struct device *, struct device *, void *);
@ -120,6 +121,11 @@ geodeide_chip_map(struct pciide_softc *sc, struct pci_attach_args *pa)
if (sc->sc_dma_ok) {
sc->sc_wdcdev.sc_atac.atac_cap = ATAC_CAP_DMA | ATAC_CAP_UDMA;
sc->sc_wdcdev.irqack = pciide_irqack;
/*
* XXXJRT What chip revisions actually need the DMA
* alignment work-around?
*/
sc->sc_wdcdev.dma_init = geodeide_dma_init;
}
sc->sc_wdcdev.sc_atac.atac_pio_cap = 4;
sc->sc_wdcdev.sc_atac.atac_dma_cap = 2;
@ -260,3 +266,18 @@ geodeide_setup_channel(struct ata_channel *chp)
idedma_ctl);
}
}
static int
geodeide_dma_init(void *v, int channel, int drive, void *databuf,
size_t datalen, int flags)
{
/*
* If the buffer is not properly aligned, we can't allow DMA
* and need to fall back to PIO.
*/
if (((uintptr_t)databuf) & 0xf)
return (EINVAL);
return (pciide_dma_init(v, channel, drive, databuf, datalen, flags));
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: atapi_wdc.c,v 1.94 2005/06/07 13:45:11 peter Exp $ */
/* $NetBSD: atapi_wdc.c,v 1.95 2005/07/06 01:46:52 thorpej Exp $ */
/*
* Copyright (c) 1998, 2001 Manuel Bouyer.
@ -30,7 +30,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: atapi_wdc.c,v 1.94 2005/06/07 13:45:11 peter Exp $");
__KERNEL_RCSID(0, "$NetBSD: atapi_wdc.c,v 1.95 2005/07/06 01:46:52 thorpej Exp $");
#ifndef ATADEBUG
#define ATADEBUG
@ -611,7 +611,7 @@ wdc_atapi_intr(struct ata_channel *chp, struct ata_xfer *xfer, int irq)
struct scsipi_xfer *sc_xfer = xfer->c_cmd;
struct ata_drive_datas *drvp = &chp->ch_drive[xfer->c_drive];
int len, phase, i, retries=0;
int ire;
int ire, error;
int dma_flags = 0;
void *cmd;
@ -692,11 +692,22 @@ again:
ATADEBUG_PRINT(("PHASE_CMDOUT\n"), DEBUG_INTR);
/* Init the DMA channel if necessary */
if (xfer->c_flags & C_DMA) {
if ((*wdc->dma_init)(wdc->dma_arg,
error = (*wdc->dma_init)(wdc->dma_arg,
chp->ch_channel, xfer->c_drive,
xfer->c_databuf, xfer->c_bcount, dma_flags) != 0) {
sc_xfer->error = XS_DRIVER_STUFFUP;
break;
xfer->c_databuf, xfer->c_bcount, dma_flags);
if (error) {
if (error == EINVAL) {
/*
* We can't do DMA on this transfer
* for some reason. Fall back to
* PIO.
*/
xfer->c_flags &= ~C_DMA;
error = 0;
} else {
sc_xfer->error = XS_DRIVER_STUFFUP;
break;
}
}
}