/*
*   DIVAPC C source
*
*   CPU.C.DMA    DMA emulation module
*
*   Versions
*
*   29-02-91 INH  Skeleton file
*   13-03-95 DAF  DMA emulator coding (GNR: Use Your Illusion II)
*   22-03-95 DAF  Made compile silently with latest IH sources
*   23-03-95 DAF  Added indirect call routine for RO RM calls.
*   23-03-95 DAF  Change which channel cascades
*   16-05-95 INH  Re-integrated with !PC v1.77
*   19-05-95 DAF  Corrected merging problems. Removed most debugging.
*   07-06-95 DAF  Attending to 16 bit DMA problems
*   10-06-95 DAF  Corrected 16 bit address calculation problems
*   13-06-95 DAF  Made undefined LS612 references not blat unknown
*		  memory!
*   14-06-95 DAF  Removed the wr8 and rd8 messages unless DEBUGing.
* 1997.05.10 W    8-bit controller mapped from 0-0xF instead of 0-0x1F
* 1997.05.14 W    Change logic so notifies only occur once per transfer.
* 1997.05.15 W    Stop DMAReqs collecting when channel is masked.

*/


/*

Questions and notes

*) 16 bit dma transfer routines must return a transfer length that
   is a multiple of 2. 08/6/95

*) The notify function is a single call given an address and a length.
   This can actually imply two tranfers with the appropriate cut in
   the carry between the 2nd and 3rd bytes (to reflect the LS612 value
   not being carried into). It is up to the registered notify function
   to deduce any implied wrap around. There can never be more one
   additional implied region for each notify call.

*) How often should the ls612 latches be read? na37 data sheet
   suggests whenever the address used for memory generates a carry
   from A7 to A8 (ie out of 1 LWB). Once the (*transfer) call.

*) How much emulation for the cascade channel? Do I refuse to use
   the 2nd controller if there isn't a channel of the 1st controller
   set into cascade mode? Presumably which channel gets used is
   pretty fixed as it corresponds to actual PCB wiring. Does the
   BIOS set up the particular cascade channel? DMA_DO_CASCADE controls
   whether we ignore the concept of cascading controllers or whether
   we emulate as fully as possible. The default is currently full
   emulation.

*) Our function call to simulate DREQ may not behave quite as the hardware
   does. If the user gets DREQ asserted and then software enables the
   relevant channel before the DREQ has been de-asserted, we might get
   things wrong. Ponder this. Maybe the first notify/transfer function back
   onto that channel means DMAAK . NA37 data sheet suggests not waiting
   for DMAAK after DREQ is going to make things go wrong anyway.

*) sw requests only work in block mode

*/

/*****************************************************************************/

#include "kernel.h"

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.syss"
#include "sys.h.hrdstate"

#define COMPILING_DMA
#include "cpu.h.dma"
#include "cpu.h.dmadefs"
#include "cpu.h.cpu"

#include <setjmp.h>
#include <stdio.h>
#include <assert.h>

/*****************************************************************************/

#if 0
extern void my_backtrace(void)
{
#define PSR 0 /*xfc000003*/

    jmp_buf jbuf;
    _kernel_unwindblock *kub = (_kernel_unwindblock *) & jbuf;
    char *lang = "bogus";

    setjmp(jbuf);

    printf("My backtrace\n");

    fflush(NULL);

    while (1)
    {
        const fp = kub->fp;
        const sp = kub->sp;
        const pc = kub->pc /*& ~ PSR*/;
        const sl = kub->sl /*& ~ PSR*/;
        char *cp = _kernel_procname(pc);

        printf("fp %x, sp %x, pc %x, sl %x, lang %s, proc %s\n", fp, sp, pc, sl, lang, cp == NULL ? "*NULL*" : cp);

        if (_kernel_unwind(kub, & lang) <= 0)
            break;
    }

    printf("End of my backtrace\n");

#undef PSR
}
#endif

/*****************************************************************************/

/* Private data */

/* The third "controller" is to direct naff accesses */
/* to so that I don't get wierd addresses and then */
/* start crapping on other memory regions! 13/6/95 */

static DMA_Controller Controller [3];




#if DEBUG

static char * reg_name_table[20] =
{
    "ch0_address   ",       /* 0 */
    "ch0_count     ",       /* 1 */
    "ch1_address   ",       /* 2 */
    "ch1_count     ",       /* 3 */
    "ch2_address   ",       /* 4 */
    "ch2_count     ",       /* 5 */
    "ch3_address   ",       /* 6 */
    "ch3_count     ",       /* 7 */

    "command_status",       /* 8 */
    "request       ",       /* 9 */
    "mask_single   ",       /* 10 */
    "mode          ",       /* 11 */
    "reset_flipflop",       /* 12 */
    "master_temp   ",       /* 13 */
    "reset_mask    ",       /* 14 */
    "mask_all      ",       /* 15 */

    "chan 0 LS612  ",       /* 16 */
    "chan 1 LS612  ",       /* 17 */
    "chan 2 LS612  ",       /* 18 */
    "chan 3 LS612  "        /* 19 */
};

static char *regname(int n)
{
    if (n < 0 || n > 19)
        return "** UNKNOWN DMA REGISTER **";
    return reg_name_table[n];
}
#endif /* DEBUG */

/*****************************************************************************/

/* Dump out our state for debugging purposes */

#include <stdio.h>

#if DEBUG

static void ds1(DMA_Controller *dcp)
{
    static char *xfer_names[4] = { "Verify", "Write", "Read", "Illegal" };
    static char *mode_names[4] = { "Demand", "Single", "Block", "Cascade" };

#define cf(x)                           \
    if (dcp->cont_flags & x)            \
        printf("%-20s: SET\n", #x);     \
    else                                \
        printf("%-20s: CLEAR\n", #x);

#define df(x)                           \
    if (chp->chan_flags & x)            \
        printf("%-20s: SET\n", #x);     \
    else                                \
        printf("%-20s: CLEAR\n", #x);

    int i;


    printf("\nController: %d bits, temp %x, mode count %d\n\n", (1+DMA_CONTNUM(dcp))*8, dcp->temp_reg, dcp->mode_count );

    cf(DMA_MEMMEM_COPY  )
    cf(DMA_CHANNEL0_HOLD)
    cf(DMA_DISABLED     )
    cf(DMA_FLIP_FLOP    )
    cf(DMA_16BIT        )

    for (i = 0; i < 4; i++)
    {
        DMA_Channel *chp = & dcp->chan[i];

        printf("\nChannel %d\n", i);

        printf("Address base %04x, current %04x, Count base %04x, current %04x\n",
            chp->addr_base, chp->addr_curr, chp->count_base, chp->count_curr);

        printf("Transfer: %s\n", xfer_names [chp->chan_flags & 3] );

        if (chp->chan_flags & DMA_AUTO_INITIALISE)
            printf("Auto initialise is active for this channel\n");
        else
            printf("No auto initialise\n");

        if (chp->chan_flags & DMA_AUTO_DEC)
            printf("Address decrements\n");
        else
            printf("Address increments\n");

        printf("Mode: %s\n", mode_names [ (chp->chan_flags >> 6) & 3 ] );

        df(DMA_REQUEST)
        df(DMA_MASK)
        df(DMA_PENDING)
        df(DMA_TC_REACHED)
        df(DMA_ACTIVE)

        printf("Current LS612 %04x, last %04x\n",
            (chp->chan_flags & DMA_LS612_MASK) >> DMA_LS612_SHIFT, chp->last_ls612);
    }

}

static void dump_state(char *msg)
{
    printf("%s\n", msg);


    ds1( & Controller[0] );
    ds1( & Controller[1] );

    printf("\n*****************************************************************************\n\n");

    fflush(stdout);
}

#endif /* DEBUG */

/*****************************************************************************/

/* Actions performed whenever a reset is performed              */
/* Reset performs the following:                                */
/*                                                              */
/* address register        unchanged                            */
/* count register          unchanged                            */
/* mode control register   all bits cleared                     */
/* device control register all bits cleared                     */
/* status register         all bits cleared                     */
/* request register        all bits cleared                     */
/* mask register           all bits set (all channels masked)   */
/* temporary register      all bits cleared                     */
/* Also keep track of whether 8 bit or 16 bit controller        */

static void reset_controller(DMA_Controller *dcp)
{
    int i;

    dcp->cont_flags = DMA_RESET_CONT_FLAGS | (DMA_CONTNUM(dcp) == 1 ? DMA_16BIT : 0);
    dcp->temp_reg = DMA_RESET_TEMP_REG;
    dcp->mode_count = DMA_RESET_MODE_COUNT;

    for (i = 0; i < 4; i++)
    {
        dcp->chan[i].chan_flags = DMA_RESET_CHAN_FLAGS;
    }
}

/* Extra reset actions that only occur on power up type resets */

static void init_controller(DMA_Controller *dcp)
{
    int i;

    for (i = 0; i < 4; i++)
    {
        dcp->chan[i].addr_curr = 0;
        dcp->chan[i].addr_base = 0;
        dcp->chan[i].count_curr = 0;
        dcp->chan[i].count_base = 0;
    }

    reset_controller(dcp);
}


/*******************************************************************************/
/* Examine each channel on the controller supplied. If controller enabled      */
/* and channel unmasked and device has _not_ just sent DMA_Request, then call  */
/* notify. If request pending or device has sent DMA_Request, then schedule    */
/* HOLD, through CPU_BusRequest, which will set callback to do DMA transfer.   */
/* Notify & Busreq specify the range of memory addresses that will be affected.*/
/* We note the value of the LS612 latch used for invalidating the memory       */
/* cache so that we can realise when we have been grossly tricked. If          */
/* this area proves problematical, complete cache flushing should be used      */
/* for all CPU_BusRequest calls. For each channel desiring attention, a        */
/* CPU_BusRequest call is issued. This is potentially excessive.               */
/*******************************************************************************/

static void check_actions(DMA_Controller *dcp)
{
    uint i; /* set scale to 2 for 16bit controller, 1 for 8bit controller */
    const int scale = (dcp->cont_flags & DMA_16BIT) ? 2 : 1;  

    if ( dcp->cont_flags & DMA_DISABLED )
    {
      #if DEBUG
        SYS_trace("Controller %d is disabled, so no follow-on actions", DMA_CONTNUM(dcp) );
      #endif
        return;
    }

#if DMA_DO_CASCADE

    if ( dcp == & Controller [ DMA_BIOS_SLAVE ]
    && ( Controller[DMA_BIOS_MASTER].chan[DMA_BIOS_CASCADE].chan_flags & DMA_CASCADE_MODE ) !=  DMA_CASCADE_MODE )
    {
      #if DEBUG
        if (DMA_TRACE_CASCADE)
        {
            SYS_trace("Access to slave DMA controller prevented by inactive BIOS cascade");
        }
      #endif
        return;
    }

#endif

    for (i = 0; i < 4; i++)      /* Over each channel */
    {
        DMA_Channel *chp = & dcp->chan[i];
        const uint ul = chp->chan_flags;
        /* if channel unmasked work out what needs to be done*/ 
        if ( (ul & DMA_MASK) == 0 ) 
        {
#if ! DMA_PARTIAL_CACHE

            /* First and last span all possible memory */
            uint first = 0u, last = 0xffffffffu;

#else /* ! DMA_PARTIAL_CODE */

#define THISPAGE        (ul & dma_mask)
#define NEXTPAGE        (THISPAGE + page_size)
#define LASTPAGE        (THISPAGE - page_size)

            const uint dma_mask = scale == 2 ? DMA_LS612_MASK16 : DMA_LS612_MASK8;
            const uint page_size = scale == 2 ? 0x20000 : 0x10000;
            const uint masked_ls612 = ul & dma_mask;

            uint first, last, base, end;
            const uint effcount = (chp->count_curr + 1) * scale;

            base = masked_ls612 | (chp->addr_curr * scale),
            end  = masked_ls612 | (chp->addr_curr * scale);

            if (ul & DMA_AUTO_DEC)
            {
                end -= effcount;

                if ( (end & dma_mask) == LASTPAGE )
                {
                    /* Fairly crude */
                    first = THISPAGE;
                    last = NEXTPAGE - 1;
                }
                else
                {
                    first = end;
                    last = base;
                }
            }
            else
            {
                end += effcount;

                if ( (end & dma_mask) == NEXTPAGE )
                {
                    /* Fairly crude */
                    first = THISPAGE;
                    last = NEXTPAGE - 1;
                }
                else
                {
                    first = base;
                    last = end;
                }
            }

#if DEBUG
            if (DMA_TRACE_ACTION)
                SYS_trace
                (
                    "check_actions #%d:%d: 0x%04x %c 0x%04x gives 0x%08x .. 0x%08x",
                    DMA_CONTNUM(dcp),
                    i,
                    chp->addr_curr * scale,
                    (ul & DMA_AUTO_DEC) ? '-' : '+',
                    effcount,
                    first,
                    last
                );
#endif

#undef THISPAGE
#undef NEXTPAGE
#undef LASTPAGE

#endif

            switch ( ul & DMA_TRANSFER_MASK /* pick out mode bits */ )
            {
                case DMA_VERIFY_TRANSFER:
                    SYS_trace("Verify transfer mode");
                    break;

                case DMA_ILLEGAL_TRANSFER:
                    SYS_trace("Illegal transfer mode");
                    break;

                /******************************/

                case DMA_WRITE_TRANSFER:
                {
                    DMA_handler *handp = & SYS_State.DMAhandlers[i + 4 * DMA_CONTNUM(dcp)];

                    #if DMA_paranoid
                    chp->last_ls612 = (BYTE) ( ul >> DMA_LS612_SHIFT );
                    #endif /* DMA_paranoid */

                    /* set callback for transfer if one is waiting */
                    if ((ul & (DMA_PENDING | DMA_REQUEST)) !=0)
                    {
                      #if DEBUG
                      SYS_trace("DMA check_actions: Do BusRequest");
                      #endif
                      CPU_BusRequest(DMAMODE_WR, first, last);
                    }
                    else
                    {
                    #if DEBUG
                      SYS_trace("DMA check_actions: Bypass BusRequest");
                    #endif
                    }
                    
                    /* if Device-started transfer not already in progress */
                    /* if it is, then device should already know */
                    if ((ul & DMA_PENDING) == 0)
                    {
                      #if DEBUG
                      SYS_trace("write notify >>>> r12=%x to %p args %08x, %08x, %08x",
                          handp->R12_val, handp->NotifyFn, (ul & DMA_MODE_MASK) >> 2, 
                          (chp->addr_curr * scale) | masked_ls612, effcount);
                      #endif
                      if (handp->R12_val == 0)
                      {
                          /* User mode/direct call */
                          (*handp->NotifyFn)
                              (
                                  (ul & DMA_MODE_MASK) >> 2,
                                  (chp->addr_curr * scale) | masked_ls612,
                                  effcount
                              );
                      }
                      else
                      {
                          /* Module/supervisor indirect call */
                          _kernel_swi_regs r;
  
                          r.r[0] = (int) ( (ul & DMA_MODE_MASK) >> 2 );
                          r.r[1] = (int) ( (chp->addr_curr * scale) | masked_ls612 );
                          r.r[2] = (int) ( effcount );
  
                          SYSs_CallViaR12( &r, ( int ) handp->NotifyFn, handp->R12_val );
                      }
                    #if DEBUG
                    SYS_trace("write notify <<<<");
                    #endif
                  }
                }
                break;

                /******************************/


                case DMA_READ_TRANSFER:
                {
                    DMA_handler *handp = & SYS_State.DMAhandlers[i + 4 * DMA_CONTNUM(dcp)];

                    #if DMA_paranoid
                    chp->last_ls612 = (BYTE) ( ul >> DMA_LS612_SHIFT );
                    #endif /* DMA_paranoid */

                    /* set callback for transfer if one is waiting */
                    if ((ul & (DMA_PENDING | DMA_REQUEST)) !=0)
                    {
                      #if DEBUG
                      SYS_trace("DMA check_actions: Do BusRequest");
                      #endif
                      CPU_BusRequest(DMAMODE_RD, first, last);
                    }
                    else
                    {
                    #if DEBUG
                      SYS_trace("DMA check_actions: Bypass BusRequest");
                    #endif
                    }

                    /* if Device-started transfer not already in progress do a notify*/
                    /* if it is, then device should already know */
                    if ((ul & DMA_PENDING) == 0)
                    {
                      #if DEBUG
                      SYS_trace("read notify >>>> r12=%x to %p args %08x, %08x, %08x",
                          handp->R12_val, handp->NotifyFn, (ul & DMA_MODE_MASK) >> 2, 
                          (chp->addr_curr * scale) | masked_ls612, effcount);
                      #endif
                      if (handp->R12_val == 0)
                      {
                          /* User mode/direct call */
                          (*handp->NotifyFn)
                              (
                                  (ul & DMA_MODE_MASK) >> 2,
                                  (chp->addr_curr * scale) | masked_ls612,
                                  effcount
                              );
                      }
                      else
                      {
                          /* Module/supervisor indirect call */
                          _kernel_swi_regs r;
               
                          r.r[0] = (int) ( (ul & DMA_MODE_MASK) >> 2 );
                          r.r[1] = (int) ( (chp->addr_curr * scale) | masked_ls612 );
                          r.r[2] = (int) ( effcount );

                          SYSs_CallViaR12( &r, ( int ) handp->NotifyFn, handp->R12_val );
                      }
                      #if DEBUG
                      SYS_trace("read notify <<<<");
                      #endif
                    }
                }
                break;

            }   /* switch on channel mode */
        }   /* if this channel not masked */
    }   /* for each channel */

}

/*****************************************************************************/
/*
 *    void (*DMA_Request) ( int n );
 *
 *   This  is  a  function provided by the DMA emulation,  which  is
 *   called  to indicate that a device wants to start a transfer  on
 *   DMA  channel  n. It is not a strict emulation of  wiggling  the
 *   equivalent  DREQ  line,  in that it  is  not  affected  by  DMA
 *   transfer mode.
 *
 *   On  receiving this call, the DMA emulation will typically  look
 *   to  see  if the channel is unmasked and ready. If so,  it  will
 *   call CPU_BusRequest(), and return.
 *
 *   The  device  may call DMA_Request() more than once  before  the
 *   corresponding  DMA  acknowledge is  generated;  this  will  not
 *   'queue up' multiple acknowledges. It is suggested that the  DMA
 *   emulation  should  not  worry  about  this,  and  simply   call
 *   CPU_BusRequest() every time, provided the channel is  unmasked.
 *   CPU_BusRequest() merely sets a flag or a callback; it is not  a
 *   time-consuming routine.
 *
 *       The DMA_Request() function will typically be called from  a
 *       memory  or  I/O  handler routine in the device  module;  it
 *       should  not  be  called asynchronously (e.g.  from  an  ARM
 *       interrupt  service routine). I will be adding a  'callback'
 *       facility to !PC, to allow asynchronous routines to give !PC
 *       a  function to be called the next time it is 'safe'  to  do
 *       so.  When this is done, DMA_Request() may be called  either
 *       by a handler or on a callback.
 *
 */

static void DMA_Request_fn(int chann)
{
    DMA_Controller *dcp = & Controller [chann >> 2];

#if DEBUG
    SYS_trace("dma request fn >>>> chan %d", chann);
#endif

    if (chann < 0 || chann > 7)
    {
      #if DMA_TRACE_REQUEST
        SYS_trace("DREQ: DMA emulation request for invalid channel %d", chann);
      #endif
        return;
    }

    if (dcp->cont_flags & DMA_DISABLED)
    {
      #if DMA_TRACE_REQUEST
        SYS_trace("DREQ: Use of DMA_Request when controller is disabled for channel %d", chann);
      #endif
    }
    else
    {
        /* Don't set the request if the channel is masked - 
           if the channel is disabled when this happens the DMAReq can hang
           around for ages and then start a transfer too early when the next transfer is
           programmed up. However the state of the Dreq line (which DMAreqs
           correspond to) should be reflected in the status register even if the channel
           is adisabled so this will now be wrong.... Is this a problem? We could have
           two flags - one for transfer pending which goes off when the channel is
           disabled, and another for the DREQ line status.  */
        if ((dcp->chan[chann & 3].chan_flags & DMA_MASK) ==0)
        {
          dcp->chan[chann & 3].chan_flags |= DMA_PENDING;
          #if DMA_TRACE_REQUEST
            SYS_trace("DREQ: Set pending action flag for channel %d", chann);
          #endif
        }
    }

    check_actions(dcp);

#if DEBUG
    SYS_trace("dma request fn <<<< chan %d", chann);
#endif
}


/* Perform regular transfer actions */

static void do_normal_channel(DMA_Controller *dcp, int chann)
{
    const int scale = (dcp->cont_flags & DMA_16BIT) ? 2 : 1;

    DMA_Channel *chp = &dcp->chan[chann];
    int left = (chp->count_curr + 1) * ( (dcp->cont_flags & DMA_16BIT) ? 2 : 1 );
    bool done = false;
    bool wrapped = false;
    bool reschedule = false;

    if ( (chp->chan_flags & DMA_TRANSFER_MASK) == DMA_ILLEGAL_TRANSFER)
    {
        SYS_trace("Illegal DMA transfer mode");
        chp->chan_flags &= ~ ( DMA_PENDING | DMA_REQUEST );
        return;
    }

    do /* while !done - ie until DMA has been completed, paused or aborted
       (in however many calls to 'transfer' that takes) */
    {
        /* Read LS612 afresh each loop iteration. */
        const uint flags = chp->chan_flags;
        const uint dma_mask = scale == 2 ? DMA_LS612_MASK16 : DMA_LS612_MASK8;
        const uint page_size = scale == 2 ? 0x20000 : 0x10000;
        const uint masked_ls612 = flags & dma_mask;
        const int dir = (flags & DMA_AUTO_DEC) ? -1 : 1;
        const uint w16 = chp->addr_curr * scale;
        const int PCaddr = masked_ls612 | w16;
        int this = left;
        int ndone;
        uint magic_rc;
        bool TCflag = false;
        BYTE *ARMaddr = CPU_PCtoARMaddr( PCaddr );
        DMA_handler *handp = & SYS_State.DMAhandlers[chann + 4 * DMA_CONTNUM(dcp)];

        /* find length that is OK to transfer */
        if (dir < 0)
        {
            if ( w16 - this /*< 0*/ >= page_size )
            {
                this = w16 + 1;
            }

            this = CPU_PCtoARMcheck( PCaddr, - this); 
        }
        else
        {
            if (w16 + this > page_size)
            {
                this = page_size - w16;
            }

            this = CPU_PCtoARMcheck( PCaddr, this);
        }

        if ( (flags & DMA_TRANSFER_MASK) == DMA_VERIFY_TRANSFER )
        {
#if DEBUG
            SYS_trace("DMA: Verify causing automatic completion");
#endif
            magic_rc = XFER_TC_BIT | this;
        }
        else
        {
#if DEBUG
            SYS_trace("transfer fn >>>> r12=%x to %p with Flags:%x PCaddr:%x Length:%x ARMaddr:%x",
                               handp->R12_val,
                               *handp->TransferFn,
                               (flags & DMA_MODE_MASK) >> DMA_MODE_SHIFT,
                               PCaddr,
                               this,
                               (int) ARMaddr);
#endif
            if (handp->R12_val == 0)
            {
                magic_rc = (*handp->TransferFn)
                           (
                               (flags & DMA_MODE_MASK) >> DMA_MODE_SHIFT,
                               PCaddr,
                               this,
                               ARMaddr
                           );
            }
            else
            {
                _kernel_swi_regs r;
                r.r[0] = (int) ( (flags & DMA_MODE_MASK) >> DMA_MODE_SHIFT );
                r.r[1] = (int) ( PCaddr );
                r.r[2] = (int) ( this );
                r.r[3] = (int) ( ARMaddr );

                magic_rc = SYSs_CallViaR12( &r, ( int ) handp->TransferFn, handp->R12_val );
            }

#if DEBUG
            SYS_trace("transfer fn <<<< with %x", magic_rc);
#endif
#if DMA_TRACE_DATA
            /* print out the data block being transferred */
            SYS_tracedata( (BYTE* ) ARMaddr, this);
#endif
        }
        /* set length transferred and flag for completion from what is returned */
        ndone = (int) (magic_rc & XFER_LEN_MASK);
        TCflag = (magic_rc & XFER_TC_BIT) ? 1 : 0;

        if ( (chp->chan_flags & DMA_TRANSFER_MASK) != DMA_VERIFY_TRANSFER )
        {

            /* Address last byte of transferred region. */
            /* Might be wrong for reverse 16 bit dma transfers! */

            dcp->temp_reg = * (ARMaddr + (dir * ndone) - dir);
            left -= ndone;

            chp->count_curr -= ndone / scale;
            chp->addr_curr += (dir * ndone) / scale;


            if (left <= 0 )
            {
                done = TCflag = wrapped = true;
#if DEBUG
            SYS_trace("DMA transfer completed");
#endif
            }
            
            if (ndone < this)
              done = true;
        }

        if (wrapped || TCflag)
        {
            chp->chan_flags |= DMA_TC_REACHED;
            chp->chan_flags &= ~ ( DMA_PENDING | DMA_REQUEST ); /* clear 'transfer happening' bits */
#if DEBUG
            SYS_trace("DMA TC reached, or count wrapped");
#endif
        }
    } while (! done);

    if (wrapped)
    {
        if ( chp->chan_flags & DMA_AUTO_INITIALISE )
        {
            chp->addr_curr = chp->addr_base;
            chp->count_curr = chp->count_base;
            reschedule = true;
#if DEBUG
            SYS_trace("auto initialised causing reschedule");
#endif
        }
    }

    if (reschedule)
    {
        check_actions(dcp);
    }
}


static void do_held0_channel(DMA_Controller *dcp, int chann)
{
    NotUsed(dcp);
    NotUsed(chann);
    SYS_trace("do_held0_channel - not written yet");
}

static void do_memmem_channel(DMA_Controller *dcp, int chann)
{
    NotUsed(dcp);
    NotUsed(chann);
    SYS_trace("do_memmem_channel - not written yet!");
}

/*
Perform whatever transfers can be performed for the specified controller.
Prioritisation is hardwired as channels 0..3. Cascading is enabled if we
are checking for it.

Do mothing if channel masked, or no transfers pending
*/

static void do_controller(DMA_Controller *dcp)
{
    int i;

    if (dcp->cont_flags & DMA_DISABLED)
    {
#if DMA_TRACE_ACTION    
        SYS_trace("do_controller %d - disabled", DMA_CONTNUM(dcp) );
#endif
        return;
    }

    for (i = 0; i < 4; i++)
    {
        DMA_Channel *chp = & dcp->chan[i];
        const uint ul = chp->chan_flags;
        
        if ( (ul & DMA_MASK) == 0 &&
             ( (ul & (DMA_REQUEST | DMA_PENDING ) ) != 0 ) )
        {
            if (i == 0 && (ul & DMA_MEMMEM_COPY) != 0)
            {
                do_memmem_channel(dcp, i);
                /* Skip channel 1 */
                i++;
            }
            else if (i == 0 && (ul & DMA_CHANNEL0_HOLD) != 0 )
            {
                do_held0_channel(dcp, i);
            }
            else
            {
                do_normal_channel(dcp, i);
            }
        }
    }
}

/*****************************************************************************/
/*
 * System calls here when the HOLD line has been asserted and HLDA has been
 * returned. We are now free to use the bus for 'DMA' actions.
 */

extern void DMA_BusAck(void)
{
    do_controller( & Controller [DMA_BIOS_MASTER] );

#if DMA_DO_CASCADE
    if ( ( Controller[DMA_BIOS_MASTER].chan[DMA_BIOS_CASCADE].chan_flags & DMA_CASCADE_MODE ) ==  DMA_CASCADE_MODE )
        do_controller( & Controller [DMA_BIOS_SLAVE] );
#else
    do_controller( & Controller [DMA_BIOS_SLAVE] );
#endif
}

/*****************************************************************************/

static void dummy_NotifyFn(uint mode, uint PCaddr, uint len)
{
    SYS_trace("\n\n#### dummy DMA NotifyFn(0x%x, 0x%x, 0x%x)", mode, PCaddr, len);
}

static uint dummy_TransferFn(uint mode, uint PCaddr, uint len, BYTE *ARMaddr)
{
    SYS_trace("\n\n#### dummy DMA TransferFn(0x%0x, 0x%x, 0x%x, 0x%p)", mode, PCaddr, len, ARMaddr);

    return len;
}

/*****************************************************************************/


static int rd8(int addr, DMA_Controller *dcp)
{
#define chfl(z)      ( dcp->chan[ (z) & 3 ].chan_flags )

    unsigned int x;     /* Avoid hassles from >> - unsigned int would be safer! */

#if DEBUG
    if ( DMA_RDTRACE & ( 1 << addr) )
    {
        SYS_trace("rd8(%s, &Controller[%d])", regname(addr), dcp - & Controller[0]);
    }
#endif

    switch (addr)
    {
        /* Read current address with flip-flop action */

        case DMA_ch0_address   : /* 0 */
        case DMA_ch1_address   : /* 2 */
        case DMA_ch2_address   : /* 4 */
        case DMA_ch3_address   : /* 6 */
            x = (addr >> 1) & 3;
            x = dcp->chan[x].addr_curr;
            x = ( dcp->cont_flags & DMA_FLIP_FLOP ) ? x >> 8 : x & 0xff;
            dcp->cont_flags ^= DMA_FLIP_FLOP;
            break;

        /* Read current counter with flip-flop action */

        case DMA_ch0_count     : /* 1 */
        case DMA_ch1_count     : /* 3 */
        case DMA_ch2_count     : /* 5 */
        case DMA_ch3_count     : /* 7 */
            x = (addr >> 1) & 3;
            x = dcp->chan[x].count_curr;
            x = ( dcp->cont_flags & DMA_FLIP_FLOP ) ? x >> 8 : x & 0xff;
            dcp->cont_flags ^= DMA_FLIP_FLOP;
            break;

        /* Read status register */

        case DMA_command_status: /* 8 */
            x = ( (chfl(0) & DMA_TC_REACHED) ?   1 : 0 ) |
                ( (chfl(1) & DMA_TC_REACHED) ?   2 : 0 ) |
                ( (chfl(2) & DMA_TC_REACHED) ?   4 : 0 ) |
                ( (chfl(3) & DMA_TC_REACHED) ?   8 : 0 ) |
                ( (chfl(0) & DMA_PENDING)    ?  16 : 0 ) |
                ( (chfl(1) & DMA_PENDING)    ?  32 : 0 ) |
                ( (chfl(2) & DMA_PENDING)    ?  64 : 0 ) |
                ( (chfl(3) & DMA_PENDING)    ? 128 : 0 ) ;
            {
                int i;
                for (i = 0; i < 4; i++)
                {
#if DEBUG
                    if ( dcp->chan[i].chan_flags & DMA_TC_REACHED )
                        SYS_trace("Clearing TC for channel %d", i);
#endif
                    dcp->chan[i].chan_flags &= ~ DMA_TC_REACHED;
                }
            }
            break;

        /* Read request register */

        case DMA_request       : /* 9 */
            #if EXTRA_DMA_REGS
              x = 0xf0                                |
                  ( (chfl(0) & DMA_REQUEST) ? 1 : 0 ) |
                  ( (chfl(1) & DMA_REQUEST) ? 2 : 0 ) |
                  ( (chfl(2) & DMA_REQUEST) ? 4 : 0 ) |
                  ( (chfl(3) & DMA_REQUEST) ? 8 : 0 ) ;
            #else
              SYS_trace("#### Read of DMA request register - return 0xFF");
              x=0xFF;
            #endif
            break;

        /* Read command/mask register */

        case DMA_mask_single   : /* 10 */
            #if EXTRA_DMA_REGS
              x = dcp->cont_flags & DMA_COMMAND_MASK;
            #else
              SYS_trace("#### Read of DMA command/mask register - return 0xFF");
              x=0xFF;
            #endif
            break;

        /* Reading the mode register involves an internal counter that steps around */
        /* the mode register value for each channel. This counter is set to zero by */
        /* a Clear Mode Register Counter command.                                   */

        case DMA_mode          : /* 11 */
            x = ( chfl( dcp->mode_count ) & DMA_MODE_MASK ) |
                ( DMA_undef & 3 );
            dcp->mode_count += 1; /* Avoid side effects */
            break;

        /* Set byte pointer flip flop */

        case DMA_reset_flipflop: /* 12 */
            #if EXTRA_DMA_REGS
              dcp->cont_flags |= DMA_FLIP_FLOP;
              x = DMA_undef & 0xff;
            #else
              SYS_trace("#### Read of DMA clear byte Flip-Flop register - return 0xFF");
              x=0xFF;
            #endif
            break;

        /* Read temporary register */

        case DMA_master_temp   : /* 13 */
            x = dcp->temp_reg;
            break;

        /* Clear mode register counter */

        case DMA_reset_mask    : /* 14 */
            #if EXTRA_DMA_REGS
              dcp->mode_count = 0;
              x = DMA_undef & 0xff;
            #else  
              SYS_trace("#### Read of DMA reset mask register - return 0xFF");
              x=0xFF;
            #endif  
            break;

        /* Read all DMA request mask register bits */

        case DMA_mask_all      : /* 15 */
            #if EXTRA_DMA_REGS
              x = 0xf0                               |
                  ( ( chfl(0) & DMA_MASK ) ? 1 : 0 ) |
                  ( ( chfl(1) & DMA_MASK ) ? 2 : 0 ) |
                  ( ( chfl(2) & DMA_MASK ) ? 4 : 0 ) |
                  ( ( chfl(3) & DMA_MASK ) ? 8 : 0 ) ;
            #else  
              SYS_trace("#### Read of DMA mask all register - return 0xFF");
              x=0xFF;
            #endif  
            break;

        /* Read the LS612 latch for a particular channel */

        case 16:                /* LS612 for channel 0/4 */
        case 17:                /* LS612 for channel 1/5 */
        case 18:                /* LS612 for channel 2/6 */
        case 19:                /* LS612 for channel 3/7 */
            x = ( DMA_LS612_MASK >> DMA_LS612_SHIFT ) & ( chfl(addr) >> DMA_LS612_SHIFT );
            break;

        default:
#if DEBUG
            SYS_trace("dma: rd8: addr 0x%x is bad", addr);
#endif

            x = 0;      /* or -1? */
            break;
    }

#if DEBUG
    if ( DMA_RDTRACE & (1 << addr) )
    {
        SYS_trace("rd8 returns 0x%02x", x);
    }
#endif

    /* Have treated this as unsigned during the function as it's more */
    /* controllable. Return it back to int here - although assuming that */
    /* only the bottom byte is actually significant. */

    return (int) x;

#undef chfl
}

/*****************************************************************************/


static void wr8(int addr, int data, DMA_Controller *dcp)
{
#define chfl(z)      ( dcp->chan[ (z) & 3 ].chan_flags )

#if DEBUG
    if ( DMA_WRTRACE & ( 1 << addr) )
    {
        SYS_trace("wr8(%s, 0x%02x, & Controller[%d]", regname(addr), data, dcp - & Controller[0] );
    }
#endif

    data &= 0xff;

    switch (addr)
    {
        /* Write current address */

        case DMA_ch0_address   : /* 0 */
        case DMA_ch1_address   : /* 2 */
        case DMA_ch2_address   : /* 4 */
        case DMA_ch3_address   : /* 6 */
            {
                DMA_Channel *chanp = & dcp->chan[ (addr >> 1) & 3 ];

                if ( dcp->cont_flags & DMA_FLIP_FLOP )
                { /* write top byte, leave bottom */
                    chanp->addr_curr = (chanp->addr_curr & 0xff) | (data << 8);
                    chanp->addr_base = (chanp->addr_base & 0xff) | (data << 8);
                }
                else
                { /* write bottom byte, leave top */
                    chanp->addr_curr = (chanp->addr_curr & 0xff00) | data;
                    chanp->addr_base = (chanp->addr_base & 0xff00) | data;
                }

                dcp->cont_flags ^= DMA_FLIP_FLOP; /* flip the flop*/
            }
            break;

        /* Write current counter */

        case DMA_ch0_count     : /* 1 */
        case DMA_ch1_count     : /* 3 */
        case DMA_ch2_count     : /* 5 */
        case DMA_ch3_count     : /* 7 */
            {
                DMA_Channel *chanp = & dcp->chan[ (addr >> 1) & 3 ];

                if ( dcp->cont_flags & DMA_FLIP_FLOP )
                { /* write top byte, leave bottom */
                    chanp->count_curr = (chanp->count_curr & 0xff) | (data << 8);
                    chanp->count_base = (chanp->count_base & 0xff) | (data << 8);
                }
                else
                { /* write bottom byte, leave top */
                    chanp->count_curr = (chanp->count_curr & 0xff00) | data;
                    chanp->count_base = (chanp->count_base & 0xff00) | data;
                }

                dcp->cont_flags ^= DMA_FLIP_FLOP; /* flip the flop*/
            }
            break;

        /* Write command register */

        case DMA_command_status: /* 8 */
            dcp->cont_flags = (dcp->cont_flags & ~ DMA_COMMAND_MASK) | (data & DMA_COMMAND_MASK);
            check_actions(dcp);
            break;

        /* Write request register */

        case DMA_request       : /* 9 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                /* can only change request bit with software if in block mode */
                if ((* ulp & DMA_MODE_MASK) == DMA_BLOCK_MODE)
                {
                  * ulp = ( * ulp & ~ DMA_REQUEST ) |
                          ( (data & 4) ? DMA_REQUEST : 0 );
                  check_actions(dcp);
                }
            }
            break;

        /* Write single bit DMA request mask register */

        case DMA_mask_single   : /* 10 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_MASK ) |
                        ( (data & 4) ? DMA_MASK : 0 );
                check_actions(dcp);
            }
            break;

        /* Write mode register */

        case DMA_mode          : /* 11 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_MODE_MASK ) |
                        ( data & DMA_MODE_MASK );
            }
            break;

        /* Clear byte pointer flip flop */

        case DMA_reset_flipflop: /* 12 */
            dcp->cont_flags &= ~ DMA_FLIP_FLOP;
            break;

        /* Master clear */

        case DMA_master_temp   : /* 13 */
            reset_controller(dcp);
            break;

        /* Clear all DMA request mask register bits */

        case DMA_reset_mask    : /* 14 */
            {
                int i;

                for (i = 0; i < 4; i++)
                {
                    dcp->chan[i].chan_flags &= ~ DMA_MASK;
                }

                check_actions(dcp);
            }
            break;

        /* Write all DMA request mask register bits */

        case DMA_mask_all      : /* 15 */
            {
                int i;

                for (i = 0; i < 4; i++)
                {
                    unsigned int *ulp = & dcp->chan[i].chan_flags;
                    * ulp = ( * ulp & ~ DMA_MASK ) |
                            ( (data & (1 << i)) ? DMA_MASK : 0 );
                }

                check_actions(dcp);
            }
            break;

        /* Write the LS612 latch for a channel */

        case 16:                /* LS612 for channel 0 */
        case 17:                /* LS612 for channel 1 */
        case 18:                /* LS612 for channel 2 */
        case 19:                /* LS612 for channel 3 */
            {
                uint *ulp = & dcp->chan[ addr & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_LS612_MASK ) |
                        ( ( data << DMA_LS612_SHIFT) & DMA_LS612_MASK );
            }

            break;

        default:
#if DEBUG
            SYS_trace("WARNING: undefined wr8 action in DMA emulation");
            SYS_trace("addr %x, data %x, controller %d", addr, data, (dcp->cont_flags & DMA_16BIT) ? 16 : 8);
#endif
          break;
    }

#if DEBUG
    if ( DMA_WRTRACE & ( 1 << addr) )
    {
        SYS_trace("wr8 leaving");
    }
#endif

#undef chfl
}

/*****************************************************************************/

/* 16 bit accesses are macro'd back onto 8 bit accesses */

static int rd16(int addr, DMA_Controller *dcp)
{
    /* Ordered */
    int x;

    /*SYS_trace("rd16(%x, %d)", addr, DMA_CONTNUM(dcp));*/

    x = rd8( addr, dcp );
    return x + ( rd8( addr + 1, dcp ) << 8 );
}

static void wr16(int addr, int data, DMA_Controller *dcp)
{
    /*SYS_trace("wr16(%x, %x, %d)", addr, data, DMA_CONTNUM(dcp));*/
    wr8(addr, data & 0xff, dcp);
    wr8(addr + 1, data >> 8, dcp);
}

/*****************************************************************************/

#define BASE        16

/* returns BASE+channel number, controller number for  valid IO addresses, 
           0xf??, 2 (dummy controller) for invalid ones */

static struct { int addr, cont; } ls612_table[16] =
{
    {   0xf01   ,   2       },
    {   BASE + 2,   0       },
    {   BASE + 3,   0       },
    {   BASE + 1,   0       },

    {   0xf02   ,   2       },
    {   0xf03   ,   2       },
    {   0xf04   ,   2       },
    {   BASE + 0,   0       },

    {   0xf05   ,   2       },
    {   BASE + 2,   1       },
    {   BASE + 3,   1       },
    {   BASE + 1,   1       },

    {   0xf06   ,   2       },
    {   0xf07   ,   2       },
    {   0xf08   ,   2       },
    {   BASE + 0,   1       }
};

/* these map the IO addresses onto controller addreses to make controller 1 &
   2 fit the same struct (controller 2 ports are spaced every 2 bytes C0-DF, 
   controller 1 ports are spaced every byte from 00-0F). 
   The LS612 latch is just bizzare, so we need a lookup table */

#define addr1(a)        ( (a) & 0xf )
#define addr2(a)        ( ((a) >> 1) & 0xf )
/*#define addrL(a)        ( ((a) & 3) | 16 )*/
#define cont1(a)        & Controller [0]
#define cont2(a)        & Controller [1]
/*#define contL(a)        & Controller [ ((a) >> 2) & 1 ]*/

static int DMA1_Rd8 (int addr) { return rd8 ( addr1(addr), cont1(addr) ); }
static int DMA1_Rd16(int addr) { return rd16( addr1(addr), cont1(addr) ); }
static int DMA2_Rd8 (int addr) { return rd8 ( addr2(addr), cont2(addr) ); }
static int DMA2_Rd16(int addr) { return rd16( addr2(addr), cont2(addr) ); }

static int DMAL_Rd8(int addr)
{
    return rd8(     ls612_table[ addr & 0xf ].addr,
                    & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static int DMAL_Rd16(int addr)
{
    return rd16(    ls612_table[ addr & 0xf ].addr,
                    & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static void DMA1_Wr8 (int addr, int data) { wr8 ( addr1(addr), data, cont1(addr) ); }
static void DMA1_Wr16(int addr, int data) { wr16( addr1(addr), data, cont1(addr) ); }
static void DMA2_Wr8 (int addr, int data) { wr8 ( addr2(addr), data, cont2(addr) ); }
static void DMA2_Wr16(int addr, int data) { wr16( addr2(addr), data, cont2(addr) ); }


static void DMAL_Wr8 (int addr, int data)
{
    wr8 ( ls612_table[ addr & 0xf ].addr , data, & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static void DMAL_Wr16 (int addr, int data)
{
    wr16 ( ls612_table[ addr & 0xf ].addr , data, & Controller [ ls612_table[ addr & 0xf ].cont ] );
}


#undef addr1
#undef addr2
#undef cont1
#undef cont2


/*****************************************************************************/

extern void DMA_Init ( void )
{
    int i;

    init_controller(& Controller[0] );
    init_controller(& Controller[1] );

    /* Emulated device request into DMA emulator */

    SYS_State.DMA_Request = & DMA_Request_fn;
    SYS_State.DMAsAvailable = 0xff;     /* 8 channels of dma */

    /* Default handlers for when not in use */

    SYS_State.NullDMAhandler.NotifyFn = & dummy_NotifyFn;
    SYS_State.NullDMAhandler.TransferFn = & dummy_TransferFn;
    SYS_State.NullDMAhandler.R12_val = 0;

    for (i = 0; i < SYS_nDMAslots; i++)
    {
        SYS_State.DMAhandlers[i] = SYS_State.NullDMAhandler;
    }

    /* Controller 1, LS612 latches, controller 2 */

    SYS_registerIO (0x000, 0x01f, DMA1_Rd8, DMA1_Rd16, DMA1_Wr8, DMA1_Wr16, DMA_r12_value);
    SYS_registerIO (0x080, 0x08f, DMAL_Rd8, DMAL_Rd16, DMAL_Wr8, DMAL_Wr16, DMA_r12_value);
    SYS_registerIO (0x0c0, 0x0df, DMA2_Rd8, DMA2_Rd16, DMA2_Wr8, DMA2_Wr16, DMA_r12_value);
}


/*****************************************************************************/

/* eof cpu/c/dma */
