/*      $NetBSD: isadma.c,v 1.68 2025/06/27 21:36:25 andvar Exp $       */

/*-
* Copyright (c) 1997, 1998, 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

/*
* Device driver for the ISA on-board DMA controller.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: isadma.c,v 1.68 2025/06/27 21:36:25 andvar Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <sys/bus.h>

#include <dev/isa/isareg.h>
#include <dev/isa/isavar.h>
#include <dev/isa/isadmavar.h>
#include <dev/isa/isadmareg.h>

struct isa_mem *isa_mem_head;

/*
* DMA Channel to Address Page Register offset mapping
*
* Offset from IO_DMAPG is stored in this 2D array -- first dimension is
* the DMA controller, second dimension is the DMA channel.
*
* e.g. dmapageport[0][1] gives us the offset for DMA ch 1 on DMA1
*/
static const int dmapageport[2][4] = {
       {0x6, 0x2, 0x0, 0x1},
       {0xe, 0xa, 0x8, 0x9}
};

static const u_int8_t dmamode[] = {
       /* write to device/read from device */
       DMA37MD_READ | DMA37MD_SINGLE,
       DMA37MD_WRITE | DMA37MD_SINGLE,

       /* write to device/read from device */
       DMA37MD_READ | DMA37MD_DEMAND,
       DMA37MD_WRITE | DMA37MD_DEMAND,

       /* write to device/read from device - DMAMODE_LOOP */
       DMA37MD_READ | DMA37MD_SINGLE | DMA37MD_LOOP,
       DMA37MD_WRITE | DMA37MD_SINGLE | DMA37MD_LOOP,

       /* write to device/read from device - DMAMODE_LOOPDEMAND */
       DMA37MD_READ | DMA37MD_DEMAND | DMA37MD_LOOP,
       DMA37MD_WRITE | DMA37MD_DEMAND | DMA37MD_LOOP,
};

static inline void _isa_dmaunmask(struct isa_dma_state *, int);
static inline void _isa_dmamask(struct isa_dma_state *, int);

static inline void
_isa_dmaunmask(struct isa_dma_state *ids, int chan)
{
       int ochan = chan & 3;

       ISA_DMA_MASK_CLR(ids, chan);

       /*
        * If DMA is frozen, don't unmask it now.  It will be
        * unmasked when DMA is thawed again.
        */
       if (ids->ids_frozen)
               return;

       /* set dma channel mode, and set dma channel mode */
       if ((chan & 4) == 0)
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_SMSK, ochan | DMA37SM_CLEAR);
       else
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_SMSK, ochan | DMA37SM_CLEAR);
}

static inline void
_isa_dmamask(struct isa_dma_state *ids, int chan)
{
       int ochan = chan & 3;

       ISA_DMA_MASK_SET(ids, chan);

       /*
        * XXX Should we avoid masking the channel if DMA is
        * XXX frozen?  It seems like what we're doing should
        * XXX be safe, and we do need to reset FFC...
        */

       /* set dma channel mode, and set dma channel mode */
       if ((chan & 4) == 0) {
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_SMSK, ochan | DMA37SM_SET);
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_FFC, 0);
       } else {
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_SMSK, ochan | DMA37SM_SET);
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_FFC, 0);
       }
}

/*
* _isa_dmainit(): Initialize the isa_dma_state for this chipset.
*/
void
_isa_dmainit(struct isa_dma_state *ids, bus_space_tag_t bst, bus_dma_tag_t dmat, device_t dev)
{
       int chan;

       ids->ids_dev = dev;

       if (ids->ids_initialized) {
               /*
                * Some systems may have e.g. `ofisa' (OpenFirmware
                * configuration of ISA bus) and a regular `isa'.
                * We allow both to call the initialization function,
                * and take the device name from the last caller
                * (assuming it will be the indirect ISA bus).  Since
                * `ofisa' and `isa' are the same bus with different
                * configuration mechanisms, the space and dma tags
                * must be the same!
                */
               if (!bus_space_is_equal(ids->ids_bst, bst) ||
                   ids->ids_dmat != dmat)
                       panic("_isa_dmainit: inconsistent ISA tags");
       } else {
               ids->ids_bst = bst;
               ids->ids_dmat = dmat;

               /*
                * Map the registers used by the ISA DMA controller.
                */
               if (bus_space_map(ids->ids_bst, IO_DMA1, DMA1_IOSIZE, 0,
                   &ids->ids_dma1h))
                       panic("_isa_dmainit: unable to map DMA controller #1");
               if (bus_space_map(ids->ids_bst, IO_DMA2, DMA2_IOSIZE, 0,
                   &ids->ids_dma2h))
                       panic("_isa_dmainit: unable to map DMA controller #2");
               if (bus_space_map(ids->ids_bst, IO_DMAPG, DMAPG_IOSIZE, 0,
                   &ids->ids_dmapgh))
                       panic("_isa_dmainit: unable to map DMA page registers");

               /*
                * All 8 DMA channels start out "masked".
                */
               ids->ids_masked = 0xff;

               /*
                * Initialize the max transfer size for each channel, if
                * it is not initialized already (i.e. by a bus-dependent
                * front-end).
                */
               for (chan = 0; chan < 8; chan++) {
                       if (ids->ids_maxsize[chan] == 0)
                               ids->ids_maxsize[chan] =
                                   ISA_DMA_MAXSIZE_DEFAULT(chan);
               }

               ids->ids_initialized = 1;

               /*
                * DRQ 4 is used to chain the two 8237s together; make
                * sure it's always cascaded, and that it will be unmasked
                * when DMA is thawed.
                */
               _isa_dmacascade(ids, 4);
       }
}

void
_isa_dmadestroy(struct isa_dma_state *ids)
{
       if (!ids->ids_initialized)
               return;

       _isa_dmacascade_stop(ids, 4);

       /*
        * Unmap the registers used by the ISA DMA controller.
        */
       bus_space_unmap(ids->ids_bst, ids->ids_dmapgh, DMAPG_IOSIZE);
       bus_space_unmap(ids->ids_bst, ids->ids_dma2h, DMA2_IOSIZE);
       bus_space_unmap(ids->ids_bst, ids->ids_dma1h, DMA1_IOSIZE);

       ids->ids_initialized = 0;
}

/*
* _isa_dmacascade(): program 8237 DMA controller channel to accept
* external dma control by a board.
*/
int
_isa_dmacascade(struct isa_dma_state *ids, int chan)
{
       int ochan = chan & 3;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               return (EINVAL);
       }

       if (!ISA_DMA_DRQ_ISFREE(ids, chan)) {
               printf("%s: DRQ %d is not free\n", device_xname(ids->ids_dev),
                   chan);
               return (EAGAIN);
       }

       ISA_DMA_DRQ_ALLOC(ids, chan);

       /* set dma channel mode, and set dma channel mode */
       if ((chan & 4) == 0)
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_MODE, ochan | DMA37MD_CASCADE);
       else
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_MODE, ochan | DMA37MD_CASCADE);

       _isa_dmaunmask(ids, chan);
       return (0);
}

/*
* _isa_dmacascade_stop(): turn off cascading on the 8237 DMA controller channel
* external dma control by a board.
*/
int
_isa_dmacascade_stop(struct isa_dma_state *ids, int chan)
{
       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               return EINVAL;
       }

       if (ISA_DMA_DRQ_ISFREE(ids, chan))
               return 0;

       _isa_dmamask(ids, chan);

       ISA_DMA_DRQ_FREE(ids, chan);

       return 0;
}

int
_isa_drq_alloc(struct isa_dma_state *ids, int chan)
{
       if (!ISA_DMA_DRQ_ISFREE(ids, chan))
               return EBUSY;
       ISA_DMA_DRQ_ALLOC(ids, chan);
       return 0;
}

int
_isa_drq_free(struct isa_dma_state *ids, int chan)
{
       if (ISA_DMA_DRQ_ISFREE(ids, chan))
               return EINVAL;
       ISA_DMA_DRQ_FREE(ids, chan);
       return 0;
}

bus_size_t
_isa_dmamaxsize(struct isa_dma_state *ids, int chan)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               return (0);
       }

       return (ids->ids_maxsize[chan]);
}

int
_isa_dmamap_create(struct isa_dma_state *ids, int chan, bus_size_t size, int flags)
{
       int error;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               return (EINVAL);
       }

       if (size > ids->ids_maxsize[chan])
               return (EINVAL);

       error = bus_dmamap_create(ids->ids_dmat, size, 1, size,
           ids->ids_maxsize[chan], flags, &ids->ids_dmamaps[chan]);

       return (error);
}

void
_isa_dmamap_destroy(struct isa_dma_state *ids, int chan)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               goto lose;
       }

       bus_dmamap_destroy(ids->ids_dmat, ids->ids_dmamaps[chan]);
       return;

lose:
       panic("_isa_dmamap_destroy");
}

/*
* _isa_dmastart(): program 8237 DMA controller channel and set it
* in motion.
*/
int
_isa_dmastart(struct isa_dma_state *ids, int chan, void *addr, bus_size_t nbytes, struct proc *p, int flags, int busdmaflags)
{
       bus_dmamap_t dmam;
       bus_addr_t dmaaddr;
       int waport;
       int ochan = chan & 3;
       int error;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               goto lose;
       }

#ifdef ISADMA_DEBUG
       printf("_isa_dmastart: drq %d, addr %p, nbytes 0x%lx, p %p, "
           "flags 0x%x, dmaflags 0x%x\n",
           chan, addr, (u_long)nbytes, p, flags, busdmaflags);
#endif

       if (ISA_DMA_DRQ_ISFREE(ids, chan)) {
               printf("%s: dma start on free channel %d\n",
                   device_xname(ids->ids_dev), chan);
               goto lose;
       }

       if (chan & 4) {
               if (nbytes > (1 << 17) || nbytes & 1 || (u_long)addr & 1) {
                       printf("%s: drq %d, nbytes 0x%lx, addr %p\n",
                           device_xname(ids->ids_dev), chan,
                           (unsigned long) nbytes, addr);
                       goto lose;
               }
       } else {
               if (nbytes > (1 << 16)) {
                       printf("%s: drq %d, nbytes 0x%lx\n",
                           device_xname(ids->ids_dev), chan,
                           (unsigned long) nbytes);
                       goto lose;
               }
       }

       dmam = ids->ids_dmamaps[chan];
       if (dmam == NULL)
               panic("_isa_dmastart: no DMA map for chan %d", chan);

       error = bus_dmamap_load(ids->ids_dmat, dmam, addr, nbytes,
           p, busdmaflags |
           ((flags & DMAMODE_READ) ? BUS_DMA_READ : BUS_DMA_WRITE));
       if (error)
               return (error);

#ifdef ISADMA_DEBUG
       __asm(".globl isa_dmastart_afterload ; isa_dmastart_afterload:");
#endif

       if (flags & DMAMODE_READ) {
               bus_dmamap_sync(ids->ids_dmat, dmam, 0, dmam->dm_mapsize,
                   BUS_DMASYNC_PREREAD);
               ids->ids_dmareads |= (1 << chan);
       } else {
               bus_dmamap_sync(ids->ids_dmat, dmam, 0, dmam->dm_mapsize,
                   BUS_DMASYNC_PREWRITE);
               ids->ids_dmareads &= ~(1 << chan);
       }

       dmaaddr = dmam->dm_segs[0].ds_addr;

#ifdef ISADMA_DEBUG
       printf("     dmaaddr %#" PRIxPADDR "\n", dmaaddr);

       __asm(".globl isa_dmastart_aftersync ; isa_dmastart_aftersync:");
#endif

       ids->ids_dmalength[chan] = nbytes;

       _isa_dmamask(ids, chan);
       ids->ids_dmafinished &= ~(1 << chan);

       if ((chan & 4) == 0) {
               /* set dma channel mode */
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h, DMA1_MODE,
                   ochan | dmamode[flags]);

               /* send start address */
               waport = DMA1_CHN(ochan);
               bus_space_write_1(ids->ids_bst, ids->ids_dmapgh,
                   dmapageport[0][ochan], (dmaaddr >> 16) & 0xff);
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h, waport,
                   dmaaddr & 0xff);
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h, waport,
                   (dmaaddr >> 8) & 0xff);

               /* send count */
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h, waport + 1,
                   (--nbytes) & 0xff);
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h, waport + 1,
                   (nbytes >> 8) & 0xff);
       } else {
               /* set dma channel mode */
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h, DMA2_MODE,
                   ochan | dmamode[flags]);

               /* send start address */
               waport = DMA2_CHN(ochan);
               bus_space_write_1(ids->ids_bst, ids->ids_dmapgh,
                   dmapageport[1][ochan], (dmaaddr >> 16) & 0xff);
               dmaaddr >>= 1;
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h, waport,
                   dmaaddr & 0xff);
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h, waport,
                   (dmaaddr >> 8) & 0xff);

               /* send count */
               nbytes >>= 1;
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h, waport + 2,
                   (--nbytes) & 0xff);
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h, waport + 2,
                   (nbytes >> 8) & 0xff);
       }

       _isa_dmaunmask(ids, chan);
       return (0);

lose:
       panic("_isa_dmastart");
}

void
_isa_dmaabort(struct isa_dma_state *ids, int chan)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmaabort");
       }

       _isa_dmamask(ids, chan);
       bus_dmamap_unload(ids->ids_dmat, ids->ids_dmamaps[chan]);
       ids->ids_dmareads &= ~(1 << chan);
}

bus_size_t
_isa_dmacount(struct isa_dma_state *ids, int chan)
{
       int waport;
       bus_size_t nbytes;
       int ochan = chan & 3;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("isa_dmacount");
       }

       _isa_dmamask(ids, chan);

       /*
        * We have to shift the byte count by 1.  If we're in auto-initialize
        * mode, the count may have wrapped around to the initial value.  We
        * can't use the TC bit to check for this case, so instead we compare
        * against the original byte count.
        * If we're not in auto-initialize mode, then the count will wrap to
        * -1, so we also handle that case.
        */
       if ((chan & 4) == 0) {
               waport = DMA1_CHN(ochan);
               nbytes = bus_space_read_1(ids->ids_bst, ids->ids_dma1h,
                   waport + 1) + 1;
               nbytes += bus_space_read_1(ids->ids_bst, ids->ids_dma1h,
                   waport + 1) << 8;
               nbytes &= 0xffff;
       } else {
               waport = DMA2_CHN(ochan);
               nbytes = bus_space_read_1(ids->ids_bst, ids->ids_dma2h,
                   waport + 2) + 1;
               nbytes += bus_space_read_1(ids->ids_bst, ids->ids_dma2h,
                   waport + 2) << 8;
               nbytes <<= 1;
               nbytes &= 0x1ffff;
       }

       if (nbytes == ids->ids_dmalength[chan])
               nbytes = 0;

       _isa_dmaunmask(ids, chan);
       return (nbytes);
}

int
_isa_dmafinished(struct isa_dma_state *ids, int chan)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmafinished");
       }

       /* check that the terminal count was reached */
       if ((chan & 4) == 0)
               ids->ids_dmafinished |= bus_space_read_1(ids->ids_bst,
                   ids->ids_dma1h, DMA1_SR) & 0x0f;
       else
               ids->ids_dmafinished |= (bus_space_read_1(ids->ids_bst,
                   ids->ids_dma2h, DMA2_SR) & 0x0f) << 4;

       return ((ids->ids_dmafinished & (1 << chan)) != 0);
}

void
_isa_dmadone(struct isa_dma_state *ids, int chan)
{
       bus_dmamap_t dmam;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmadone");
       }

       dmam = ids->ids_dmamaps[chan];

       _isa_dmamask(ids, chan);

       if (_isa_dmafinished(ids, chan) == 0)
               printf("%s: _isa_dmadone: channel %d not finished\n",
                   device_xname(ids->ids_dev), chan);

       bus_dmamap_sync(ids->ids_dmat, dmam, 0, dmam->dm_mapsize,
           (ids->ids_dmareads & (1 << chan)) ? BUS_DMASYNC_POSTREAD :
           BUS_DMASYNC_POSTWRITE);

       bus_dmamap_unload(ids->ids_dmat, dmam);
       ids->ids_dmareads &= ~(1 << chan);
}

void
_isa_dmafreeze(struct isa_dma_state *ids)
{
       int s;

       s = splhigh();

       if (ids->ids_frozen == 0) {
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_MASK, 0x0f);
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_MASK, 0x0f);
       }

       ids->ids_frozen++;
       if (ids->ids_frozen < 1)
               panic("_isa_dmafreeze: overflow");

       splx(s);
}

void
_isa_dmathaw(struct isa_dma_state *ids)
{
       int s;

       s = splhigh();

       ids->ids_frozen--;
       if (ids->ids_frozen < 0)
               panic("_isa_dmathaw: underflow");

       if (ids->ids_frozen == 0) {
               bus_space_write_1(ids->ids_bst, ids->ids_dma1h,
                   DMA1_MASK, ids->ids_masked & 0x0f);
               bus_space_write_1(ids->ids_bst, ids->ids_dma2h,
                   DMA2_MASK, (ids->ids_masked >> 4) & 0x0f);
       }

       splx(s);
}

int
_isa_dmamem_alloc(struct isa_dma_state *ids, int chan, bus_size_t size, bus_addr_t *addrp, int flags)
{
       bus_dma_segment_t seg;
       int error, boundary, rsegs;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmamem_alloc");
       }

       boundary = (chan & 4) ? (1 << 17) : (1 << 16);

       size = round_page(size);

       error = bus_dmamem_alloc(ids->ids_dmat, size, PAGE_SIZE, boundary,
           &seg, 1, &rsegs, flags);
       if (error)
               return (error);

       *addrp = seg.ds_addr;
       return (0);
}

void
_isa_dmamem_free(struct isa_dma_state *ids, int chan, bus_addr_t addr, bus_size_t size)
{
       bus_dma_segment_t seg;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmamem_free");
       }

       seg.ds_addr = addr;
       seg.ds_len = size;

       bus_dmamem_free(ids->ids_dmat, &seg, 1);
}

int
_isa_dmamem_map(struct isa_dma_state *ids, int chan, bus_addr_t addr, bus_size_t size, void **kvap, int flags)
{
       bus_dma_segment_t seg;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmamem_map");
       }

       seg.ds_addr = addr;
       seg.ds_len = size;

       return (bus_dmamem_map(ids->ids_dmat, &seg, 1, size, kvap, flags));
}

void
_isa_dmamem_unmap(struct isa_dma_state *ids, int chan, void *kva, size_t size)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmamem_unmap");
       }

       bus_dmamem_unmap(ids->ids_dmat, kva, size);
}

paddr_t
_isa_dmamem_mmap(struct isa_dma_state *ids, int chan, bus_addr_t addr, bus_size_t size, off_t off, int prot, int flags)
{
       bus_dma_segment_t seg;

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_dmamem_mmap");
       }

       if (off < 0)
               return (-1);

       seg.ds_addr = addr;
       seg.ds_len = size;

       return (bus_dmamem_mmap(ids->ids_dmat, &seg, 1, off, prot, flags));
}

int
_isa_drq_isfree(struct isa_dma_state *ids, int chan)
{

       if (chan < 0 || chan > 7) {
               printf("%s: bogus drq %d\n", device_xname(ids->ids_dev), chan);
               panic("_isa_drq_isfree");
       }

       return ISA_DMA_DRQ_ISFREE(ids, chan);
}

void *
_isa_malloc(struct isa_dma_state *ids, int chan, size_t size, struct malloc_type *pool, int flags)
{
       bus_addr_t addr;
       void *kva;
       int bflags;
       struct isa_mem *m;

       bflags = flags & M_WAITOK ? BUS_DMA_WAITOK : BUS_DMA_NOWAIT;

       if (_isa_dmamem_alloc(ids, chan, size, &addr, bflags))
               return 0;
       if (_isa_dmamem_map(ids, chan, addr, size, &kva, bflags)) {
               _isa_dmamem_free(ids, chan, addr, size);
               return 0;
       }
       m = malloc(sizeof(*m), pool, flags);
       if (m == 0) {
               _isa_dmamem_unmap(ids, chan, kva, size);
               _isa_dmamem_free(ids, chan, addr, size);
               return 0;
       }
       m->ids = ids;
       m->chan = chan;
       m->size = size;
       m->addr = addr;
       m->kva = kva;
       m->next = isa_mem_head;
       isa_mem_head = m;
       return (void *)kva;
}

void
_isa_free(void *addr, struct malloc_type *pool)
{
       struct isa_mem **mp, *m;
       void *kva = (void *)addr;

       for(mp = &isa_mem_head; *mp && (*mp)->kva != kva;
           mp = &(*mp)->next)
               ;
       m = *mp;
       if (!m) {
               printf("_isa_free: freeing unallocated memory\n");
               return;
       }
       *mp = m->next;
       _isa_dmamem_unmap(m->ids, m->chan, kva, m->size);
       _isa_dmamem_free(m->ids, m->chan, m->addr, m->size);
       free(m, pool);
}

paddr_t
_isa_mappage(void *mem, off_t off, int prot)
{
       struct isa_mem *m;

       for(m = isa_mem_head; m && m->kva != (void *)mem; m = m->next)
               ;
       if (!m) {
               printf("_isa_mappage: mapping unallocated memory\n");
               return -1;
       }
       return _isa_dmamem_mmap(m->ids, m->chan, m->addr,
           m->size, off, prot, BUS_DMA_WAITOK);
}