Many older revisions of the Acer Labs M5229 UDMA IDE controller have a
hardware bug that makes DMA fail for LBA48 commands fail.  As a result
accessing data on disks bigger than 137 GB beyond the 137 GB boundary
will fail.  A workaround for this is to fall back to PIO, but this is
quite a bit slower than DMA (3 MB/s vs. 15 MB/s on my setup).  A much
better approach would be to use DMA for everything below the 137 GB
boundary and PIO for everything above.  I never found an elegant way
to do that though.

Recently, Takeshi Nakayama did post a diff to the NetBSD sparc64
mailing list that has a fairly elegant way to fix this:

http://mail-index.netbsd.org/port-sparc64/2010/10/26/msg001398.html

Here's the equivalent diff for our tree.  With this diff, I can access
everything on a 160 GB disk.  Accessing the tail end is quite a bit
slower than the start of the disk.  For that reason NetBSD prints a
warning in dmesg.  I chose not to do that for now, but I can probably
be convinced to add a printf here.

ok?


Index: ata/ata_wdc.c
===================================================================
RCS file: /cvs/src/sys/dev/ata/ata_wdc.c,v
retrieving revision 1.34
diff -u -p -r1.34 ata_wdc.c
--- ata/ata_wdc.c       23 Jul 2010 07:47:12 -0000      1.34
+++ ata/ata_wdc.c       5 Nov 2010 22:18:48 -0000
@@ -167,7 +167,7 @@ _wdc_ata_bio_start(struct channel_softc 
        u_int8_t head, sect, cmd = 0;
        int nblks;
        int ata_delay;
-       int dma_flags = 0;
+       int error, dma_flags = 0;
 
        WDCDEBUG_PRINT(("_wdc_ata_bio_start %s:%d:%d\n",
            chp->wdc->sc_dev.dv_xname, chp->channel, xfer->drive),
@@ -249,10 +249,21 @@ again:
                                cmd = (ata_bio->flags & ATA_READ) ?
                                    WDCC_READDMA : WDCC_WRITEDMA;
                        /* Init the DMA channel. */
-                       if ((*chp->wdc->dma_init)(chp->wdc->dma_arg,
+                       error = (*chp->wdc->dma_init)(chp->wdc->dma_arg,
                            chp->channel, xfer->drive,
                            (char *)xfer->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);
@@ -276,6 +287,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);
Index: pci/pciide.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/pciide.c,v
retrieving revision 1.321
diff -u -p -r1.321 pciide.c
--- pci/pciide.c        31 Aug 2010 17:13:44 -0000      1.321
+++ pci/pciide.c        5 Nov 2010 22:18:51 -0000
@@ -215,6 +215,7 @@ void ns_scx200_setup_channel(struct chan
 void acer_chip_map(struct pciide_softc *, struct pci_attach_args *);
 void acer_setup_channel(struct channel_softc *);
 int  acer_pci_intr(void *);
+int  acer_dma_init(void *, int, int, void *, size_t, int);
 
 void pdc202xx_chip_map(struct pciide_softc *, struct pci_attach_args *);
 void pdc202xx_setup_channel(struct channel_softc *);
@@ -5629,6 +5630,8 @@ acer_chip_map(struct pciide_softc *sc, s
                }
                sc->sc_wdcdev.cap |= WDC_CAPABILITY_IRQACK;
                sc->sc_wdcdev.irqack = pciide_irqack;
+               if (rev <= 0xC4)
+                       sc->sc_wdcdev.dma_init = acer_dma_init;
        }
 
        sc->sc_wdcdev.PIO_cap = 4;
@@ -5821,6 +5824,17 @@ acer_pci_intr(void *arg)
                }
        }
        return (rv);
+}
+
+int
+acer_dma_init(void *v, int channel, int drive, void *databuf,
+    size_t datalen, int flags)
+{
+       /* Use PIO for LBA48 transfers. */
+       if (flags & WDC_DMA_LBA48)
+               return (EINVAL);
+
+       return (pciide_dma_init(v, channel, drive, databuf, datalen, flags));
 }
 
 void

Reply via email to