/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the Intel DMA controllers.
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *          Miran Grca, <mgrca8@gmail.com>
 *          Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 2008-2020 Sarah Walker.
 *          Copyright 2016-2020 Miran Grca.
 *          Copyright 2017-2020 Fred N. van Kempen.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include "x86.h"
#include <86box/machine.h>
#include <86box/mca.h>
#include <86box/mem.h>
#include <86box/io.h>
#include <86box/pic.h>
#include <86box/dma.h>
#include <86box/plat_unused.h>

dma_t   dma[8];
uint8_t dma_e;
uint8_t dma_m;

static uint8_t  dmaregs[3][16];
static int      dma_wp[2];
static uint8_t  dma_stat;
static uint8_t  dma_stat_rq;
static uint8_t  dma_stat_rq_pc;
static uint8_t  dma_stat_adv_pend;
static uint8_t  dma_command[2];
static uint8_t  dma_req_is_soft;
static uint8_t  dma_advanced;
static uint8_t  dma_at;
static uint8_t  dma_buffer[65536];
static uint16_t dma_sg_base;
static uint16_t dma16_buffer[65536];
static uint32_t dma_mask;

static struct dma_ps2_t {
    int xfr_command;
    int xfr_channel;
    int byte_ptr;

    int is_ps2;
} dma_ps2;

#define DMA_PS2_IOA            (1 << 0)
#define DMA_PS2_AUTOINIT       (1 << 1)
#define DMA_PS2_XFER_MEM_TO_IO (1 << 2)
#define DMA_PS2_XFER_IO_TO_MEM (3 << 2)
#define DMA_PS2_XFER_MASK      (3 << 2)
#define DMA_PS2_DEC2           (1 << 4)
#define DMA_PS2_SIZE16         (1 << 6)

#ifdef ENABLE_DMA_LOG
int dma_do_log = ENABLE_DMA_LOG;

static void
dma_log(const char *fmt, ...)
{
    va_list ap;

    if (dma_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define dma_log(fmt, ...)
#endif

static void dma_ps2_run(int channel);

int
dma_get_drq(int channel)
{
    return !!(dma_stat_rq_pc & (1 << channel));
}

void
dma_set_drq(int channel, int set)
{
    dma_stat_rq_pc &= ~(1 << channel);
    if (set)
        dma_stat_rq_pc |= (1 << channel);
}

static int
dma_transfer_size(dma_t *dev)
{
    return dev->transfer_mode & 0xff;
}

static void
dma_sg_next_addr(dma_t *dev)
{
    int ts = dma_transfer_size(dev);

    dma_bm_read(dev->ptr_cur, (uint8_t *) &(dev->addr), 4, ts);
    dma_bm_read(dev->ptr_cur + 4, (uint8_t *) &(dev->count), 4, ts);
    dma_log("DMA S/G DWORDs: %08X %08X\n", dev->addr, dev->count);
    dev->eot = dev->count >> 31;
    dev->count &= 0xfffe;
    dev->cb = (uint16_t) dev->count;
    dev->cc = dev->count;
    if (!dev->count)
        dev->count = 65536;
    if (ts == 2)
        dev->addr &= 0xfffffffe;
    dev->ab   = dev->addr & dma_mask;
    dev->ac   = dev->addr & dma_mask;
    dev->page = dev->page_l = (dev->ac >> 16) & 0xff;
    dev->page_h             = (dev->ac >> 24) & 0xff;
    dev->ptr_cur += 8;
}

static void
dma_block_transfer(int channel)
{
    int bit16 = (channel >= 4);

    if (dma_advanced)
        bit16 = !!(dma_transfer_size(&(dma[channel])) == 2);

    dma_req_is_soft = 1;
    for (uint16_t i = 0; i <= dma[channel].cb; i++) {
        if ((dma[channel].mode & 0x8c) == 0x84) {
            if (bit16)
                dma_channel_write(channel, dma16_buffer[i]);
            else
                dma_channel_write(channel, dma_buffer[i]);
        } else if ((dma[channel].mode & 0x8c) == 0x88) {
            if (bit16)
                dma16_buffer[i] = dma_channel_read(channel);
            else
                dma_buffer[i] = dma_channel_read(channel);
        }
    }
    dma_req_is_soft = 0;
}

static void
dma_mem_to_mem_transfer(void)
{
    int i;

    if ((dma[0].mode & 0x0c) != 0x08)
        fatal("DMA memory to memory transfer: channel 0 mode not read\n");
    if ((dma[1].mode & 0x0c) != 0x04)
        fatal("DMA memory to memory transfer: channel 1 mode not write\n");

    dma_req_is_soft = 1;

    for (i = 0; i <= dma[0].cb; i++)
        dma_buffer[i] = dma_channel_read(0);

    for (i = 0; i <= dma[1].cb; i++)
        dma_channel_write(1, dma_buffer[i]);

    dma_req_is_soft = 0;
}

static void
dma_sg_write(uint16_t port, uint8_t val, void *priv)
{
    dma_t *dev = (dma_t *) priv;

    dma_log("DMA S/G BYTE  write: %04X       %02X\n", port, val);

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x00:
            dma_log("DMA S/G Cmd   : val = %02X, old = %02X\n", val, dev->sg_command);
            if ((val & 1) && !(dev->sg_command & 1)) { /*Start*/
#ifdef ENABLE_DMA_LOG
                dma_log("DMA S/G start\n");
#endif
                dev->ptr_cur = dev->ptr;
                dma_sg_next_addr(dev);
                dev->sg_status = (dev->sg_status & 0xf7) | 0x01;
            }
            if (!(val & 1) && (dev->sg_command & 1)) { /*Stop*/
#ifdef ENABLE_DMA_LOG
                dma_log("DMA S/G stop\n");
#endif
                dev->sg_status &= ~0x81;
            }

            dev->sg_command = val;
            break;
        case 0x20:
            dev->ptr = (dev->ptr & 0xffffff00) | (val & 0xfc);
            dev->ptr %= (mem_size * 1024);
            dev->ptr0 = val;
            break;
        case 0x21:
            dev->ptr = (dev->ptr & 0xffff00fc) | (val << 8);
            dev->ptr %= (mem_size * 1024);
            break;
        case 0x22:
            dev->ptr = (dev->ptr & 0xff00fffc) | (val << 16);
            dev->ptr %= (mem_size * 1024);
            break;
        case 0x23:
            dev->ptr = (dev->ptr & 0x00fffffc) | (val << 24);
            dev->ptr %= (mem_size * 1024);
            break;

        default:
            break;
    }
}

static void
dma_sg_writew(uint16_t port, uint16_t val, void *priv)
{
    dma_t *dev = (dma_t *) priv;

    dma_log("DMA S/G WORD  write: %04X     %04X\n", port, val);

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x00:
            dma_sg_write(port, val & 0xff, priv);
            break;
        case 0x20:
            dev->ptr = (dev->ptr & 0xffff0000) | (val & 0xfffc);
            dev->ptr %= (mem_size * 1024);
            dev->ptr0 = val & 0xff;
            break;
        case 0x22:
            dev->ptr = (dev->ptr & 0x0000fffc) | (val << 16);
            dev->ptr %= (mem_size * 1024);
            break;

        default:
            break;
    }
}

static void
dma_sg_writel(uint16_t port, uint32_t val, void *priv)
{
    dma_t *dev = (dma_t *) priv;

    dma_log("DMA S/G DWORD write: %04X %08X\n", port, val);

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x00:
            dma_sg_write(port, val & 0xff, priv);
            break;
        case 0x20:
            dev->ptr = (val & 0xfffffffc);
            dev->ptr %= (mem_size * 1024);
            dev->ptr0 = val & 0xff;
            break;

        default:
            break;
    }
}

static uint8_t
dma_sg_read(uint16_t port, void *priv)
{
    const dma_t *dev = (dma_t *) priv;

    uint8_t ret = 0xff;

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x08:
            ret = (dev->sg_status & 0x01);
            if (dev->eot)
                ret |= 0x80;
            if ((dev->sg_command & 0xc0) == 0x40)
                ret |= 0x20;
            if (dev->ab != 0x00000000)
                ret |= 0x08;
            if (dev->ac != 0x00000000)
                ret |= 0x04;
            break;
        case 0x20:
            ret = dev->ptr0;
            break;
        case 0x21:
            ret = dev->ptr >> 8;
            break;
        case 0x22:
            ret = dev->ptr >> 16;
            break;
        case 0x23:
            ret = dev->ptr >> 24;
            break;

        default:
            break;
    }

    dma_log("DMA S/G BYTE  read : %04X       %02X\n", port, ret);

    return ret;
}

static uint16_t
dma_sg_readw(uint16_t port, void *priv)
{
    const dma_t *dev = (dma_t *) priv;

    uint16_t ret = 0xffff;

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x08:
            ret = (uint16_t) dma_sg_read(port, priv);
            break;
        case 0x20:
            ret = dev->ptr0 | (dev->ptr & 0xff00);
            break;
        case 0x22:
            ret = dev->ptr >> 16;
            break;

        default:
            break;
    }

    dma_log("DMA S/G WORD  read : %04X     %04X\n", port, ret);

    return ret;
}

static uint32_t
dma_sg_readl(uint16_t port, void *priv)
{
    const dma_t *dev = (dma_t *) priv;

    uint32_t ret = 0xffffffff;

    port &= 0xff;

    if (port < 0x20)
        port &= 0xf8;
    else
        port &= 0xe3;

    switch (port) {
        case 0x08:
            ret = (uint32_t) dma_sg_read(port, priv);
            break;
        case 0x20:
            ret = dev->ptr0 | (dev->ptr & 0xffffff00);
            break;

        default:
            break;
    }

    dma_log("DMA S/G DWORD read : %04X %08X\n", port, ret);

    return ret;
}

static void
dma_ext_mode_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    int channel = (val & 0x03);

    if (addr == 0x4d6)
        channel |= 4;

    dma[channel].ext_mode = val & 0x7c;

    switch ((val > 2) & 0x03) {
        case 0x00:
            dma[channel].transfer_mode = 0x0101;
            break;
        case 0x01:
            dma[channel].transfer_mode = 0x0202;
            break;
        case 0x02: /* 0x02 is reserved. */
            /* Logic says this should be an undocumented mode that counts by words,
               but is 8-bit I/O, thus only transferring every second byte. */
            dma[channel].transfer_mode = 0x0201;
            break;
        case 0x03:
            dma[channel].transfer_mode = 0x0102;
            break;

        default:
            break;
    }
}

static uint8_t
dma_sg_int_status_read(UNUSED(uint16_t addr), UNUSED(void *priv))
{
    uint8_t ret = 0x00;

    for (uint8_t i = 0; i < 8; i++) {
        if (i != 4)
            ret = (!!(dma[i].sg_status & 8)) << i;
    }

    return ret;
}

static uint8_t
dma_read(uint16_t addr, UNUSED(void *priv))
{
    int     channel = (addr >> 1) & 3;
    int     count;
    uint8_t ret = (dmaregs[0][addr & 0xf]);

    switch (addr & 0xf) {
        case 0:
        case 2:
        case 4:
        case 6: /*Address registers*/
            dma_wp[0] ^= 1;
            if (dma_wp[0])
                ret = (dma[channel].ac & 0xff);
            else
                ret = ((dma[channel].ac >> 8) & 0xff);
            break;

        case 1:
        case 3:
        case 5:
        case 7: /*Count registers*/
            dma_wp[0] ^= 1;
            count = dma[channel].cc/* + 1*/;
            if (dma_wp[0])
                ret = count & 0xff;
            else
                ret = count >> 8;
            break;

        case 8: /*Status register*/
            ret = dma_stat_rq_pc & 0xf;
            ret <<= 4;
            ret |= dma_stat & 0xf;
            dma_stat &= ~0xf;
            break;

        case 0xd: /*Temporary register*/
            ret = 0x00;
            break;

        default:
            break;
    }

    dma_log("DMA: [R] %04X = %02X\n", addr, ret);

    return ret;
}

static void
dma_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    int channel = (addr >> 1) & 3;

    dma_log("DMA: [W] %04X = %02X\n", addr, val);

    dmaregs[0][addr & 0xf] = val;
    switch (addr & 0xf) {
        case 0:
        case 2:
        case 4:
        case 6: /*Address registers*/
            dma_wp[0] ^= 1;
            if (dma_wp[0])
                dma[channel].ab = (dma[channel].ab & 0xffffff00 & dma_mask) | val;
            else
                dma[channel].ab = (dma[channel].ab & 0xffff00ff & dma_mask) | (val << 8);
            dma[channel].ac = dma[channel].ab;
            return;

        case 1:
        case 3:
        case 5:
        case 7: /*Count registers*/
            dma_wp[0] ^= 1;
            if (dma_wp[0])
                dma[channel].cb = (dma[channel].cb & 0xff00) | val;
            else
                dma[channel].cb = (dma[channel].cb & 0x00ff) | (val << 8);
            dma[channel].cc = dma[channel].cb;
            return;

        case 8: /*Control register*/
            dma_command[0] = val;
#ifdef ENABLE_DMA_LOG
            if (val & 0x01)
                dma_log("[%08X:%04X] Memory-to-memory enable\n", CS, cpu_state.pc);
#endif
            return;

        case 9: /*Request register */
            channel = (val & 3);
            if (val & 4) {
                dma_stat_rq_pc |= (1 << channel);
                if ((channel == 0) && (dma_command[0] & 0x01)) {
                    dma_log("Memory to memory transfer start\n");
                    dma_mem_to_mem_transfer();
                } else
                    dma_block_transfer(channel);
            } else
                dma_stat_rq_pc &= ~(1 << channel);
            break;

        case 0xa: /*Mask*/
            channel = (val & 3);
            if (val & 4)
                dma_m |= (1 << channel);
            else
                dma_m &= ~(1 << channel);
            return;

        case 0xb: /*Mode*/
            channel           = (val & 3);
            dma[channel].mode = val;
            if (dma_ps2.is_ps2) {
                dma[channel].ps2_mode &= ~0x1c;
                if (val & 0x20)
                    dma[channel].ps2_mode |= 0x10;
                if ((val & 0xc) == 8)
                    dma[channel].ps2_mode |= 4;
                else if ((val & 0xc) == 4)
                    dma[channel].ps2_mode |= 0xc;
            }
            return;

        case 0xc: /*Clear FF*/
            dma_wp[0] = 0;
            return;

        case 0xd: /*Master clear*/
            dma_wp[0] = 0;
            dma_m |= 0xf;
            dma_stat_rq_pc &= ~0x0f;
            return;

        case 0xe: /*Clear mask*/
            dma_m &= 0xf0;
            return;

        case 0xf: /*Mask write*/
            dma_m = (dma_m & 0xf0) | (val & 0xf);
            return;

        default:
            break;
    }
}

static uint8_t
dma_ps2_read(uint16_t addr, UNUSED(void *priv))
{
    const dma_t  *dma_c = &dma[dma_ps2.xfr_channel];
    uint8_t temp  = 0xff;

    switch (addr) {
        case 0x1a:
            switch (dma_ps2.xfr_command) {
                case 2: /*Address*/
                case 3:
                    switch (dma_ps2.byte_ptr) {
                        case 0:
                            temp             = dma_c->ac & 0xff;
                            dma_ps2.byte_ptr = 1;
                            break;
                        case 1:
                            temp             = (dma_c->ac >> 8) & 0xff;
                            dma_ps2.byte_ptr = 2;
                            break;
                        case 2:
                            temp             = (dma_c->ac >> 16) & 0xff;
                            dma_ps2.byte_ptr = 0;
                            break;

                        default:
                            break;
                    }
                    break;

                case 4: /*Count*/
                case 5:
                    if (dma_ps2.byte_ptr)
                        temp = dma_c->cc >> 8;
                    else
                        temp = dma_c->cc & 0xff;
                    dma_ps2.byte_ptr = (dma_ps2.byte_ptr + 1) & 1;
                    break;

                case 6: /*Read DMA status*/
                    if (dma_ps2.byte_ptr) {
                        temp = ((dma_stat_rq & 0xf0) >> 4) | (dma_stat & 0xf0);
                        dma_stat &= ~0xf0;
                        dma_stat_rq &= ~0xf0;
                    } else {
                        temp = (dma_stat_rq & 0xf) | ((dma_stat & 0xf) << 4);
                        dma_stat &= ~0xf;
                        dma_stat_rq &= ~0xf;
                    }
                    dma_ps2.byte_ptr = (dma_ps2.byte_ptr + 1) & 1;
                    break;

                case 7: /*Mode*/
                    temp = dma_c->ps2_mode;
                    break;

                case 8: /*Arbitration Level*/
                    temp = dma_c->arb_level;
                    break;

                case 9: /*Set DMA mask*/
                    dma_m |= (1 << dma_ps2.xfr_channel);
                    break;

                case 0xa: /*Reset DMA mask*/
                    dma_m &= ~(1 << dma_ps2.xfr_channel);
                    break;

                case 0xb:
                    if (!(dma_m & (1 << dma_ps2.xfr_channel)))
                        dma_ps2_run(dma_ps2.xfr_channel);
                    break;

                default:
                    fatal("Bad XFR Read command %i channel %i\n", dma_ps2.xfr_command, dma_ps2.xfr_channel);
            }
            break;

        default:
            break;
    }
    return temp;
}

static void
dma_ps2_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    dma_t  *dma_c = &dma[dma_ps2.xfr_channel];
    uint8_t mode;

    switch (addr) {
        case 0x18:
            dma_ps2.xfr_channel = val & 0x7;
            dma_ps2.xfr_command = val >> 4;
            dma_ps2.byte_ptr    = 0;
            switch (dma_ps2.xfr_command) {
                case 9: /*Set DMA mask*/
                    dma_m |= (1 << dma_ps2.xfr_channel);
                    break;

                case 0xa: /*Reset DMA mask*/
                    dma_m &= ~(1 << dma_ps2.xfr_channel);
                    break;

                case 0xb:
                    if (!(dma_m & (1 << dma_ps2.xfr_channel)))
                        dma_ps2_run(dma_ps2.xfr_channel);
                    break;

                default:
                    break;
            }
            break;

        case 0x1a:
            switch (dma_ps2.xfr_command) {
                case 0: /*I/O address*/
                    if (dma_ps2.byte_ptr)
                        dma_c->io_addr = (dma_c->io_addr & 0x00ff) | (val << 8);
                    else
                        dma_c->io_addr = (dma_c->io_addr & 0xff00) | val;
                    dma_ps2.byte_ptr = (dma_ps2.byte_ptr + 1) & 1;
                    break;

                case 2: /*Address*/
                    switch (dma_ps2.byte_ptr) {
                        case 0:
                            dma_c->ac        = (dma_c->ac & 0xffff00) | val;
                            dma_ps2.byte_ptr = 1;
                            break;

                        case 1:
                            dma_c->ac        = (dma_c->ac & 0xff00ff) | (val << 8);
                            dma_ps2.byte_ptr = 2;
                            break;

                        case 2:
                            dma_c->ac        = (dma_c->ac & 0x00ffff) | (val << 16);
                            dma_ps2.byte_ptr = 0;
                            break;

                        default:
                            break;
                    }
                    dma_c->ab = dma_c->ac;
                    break;

                case 4: /*Count*/
                    if (dma_ps2.byte_ptr)
                        dma_c->cc = (dma_c->cc & 0xff) | (val << 8);
                    else
                        dma_c->cc = (dma_c->cc & 0xff00) | val;
                    dma_ps2.byte_ptr = (dma_ps2.byte_ptr + 1) & 1;
                    dma_c->cb        = dma_c->cc;
                    break;

                case 7: /*Mode register*/
                    mode = 0;
                    if (val & DMA_PS2_DEC2)
                        mode |= 0x20;
                    if ((val & DMA_PS2_XFER_MASK) == DMA_PS2_XFER_MEM_TO_IO)
                        mode |= 8;
                    else if ((val & DMA_PS2_XFER_MASK) == DMA_PS2_XFER_IO_TO_MEM)
                        mode |= 4;
                    dma_c->mode = (dma_c->mode & ~0x2c) | mode;
                    if (val & DMA_PS2_AUTOINIT)
                        dma_c->mode |= 0x10;
                    dma_c->ps2_mode = val;
                    dma_c->size     = val & DMA_PS2_SIZE16;
                    break;

                case 8: /*Arbitration Level*/
                    dma_c->arb_level = val;
                    break;

                case 9: /*Set DMA mask*/
                    dma_m |= (1 << dma_ps2.xfr_channel);
                    break;

                case 0xa: /*Reset DMA mask*/
                    dma_m &= ~(1 << dma_ps2.xfr_channel);
                    break;

                case 0xb:
                    if (!(dma_m & (1 << dma_ps2.xfr_channel)))
                        dma_ps2_run(dma_ps2.xfr_channel);
                    break;

                default:
                    fatal("Bad XFR command %i channel %i val %02x\n", dma_ps2.xfr_command, dma_ps2.xfr_channel, val);
            }
            break;

        default:
            break;
    }
}

static uint8_t
dma16_read(uint16_t addr, UNUSED(void *priv))
{
    int     channel = ((addr >> 2) & 3) + 4;
#ifdef ENABLE_DMA_LOG
    uint16_t port = addr;
#endif
    uint8_t ret;
    int count;

    addr >>= 1;

    ret = dmaregs[1][addr & 0xf];

    switch (addr & 0xf) {
        case 0:
        case 2:
        case 4:
        case 6: /*Address registers*/
            dma_wp[1] ^= 1;
            if (dma_ps2.is_ps2) {
                if (dma_wp[1])
                    ret = (dma[channel].ac);
                else
                    ret = ((dma[channel].ac >> 8) & 0xff);
            } else if (dma_wp[1])
                ret = ((dma[channel].ac >> 1) & 0xff);
            else
                ret = ((dma[channel].ac >> 9) & 0xff);
            break;

        case 1:
        case 3:
        case 5:
        case 7: /*Count registers*/
            dma_wp[1] ^= 1;
            count = dma[channel].cc/* + 1*/;
            if (dma_wp[1])
                ret = count & 0xff;
            else
                ret = count >> 8;
            break;

        case 8: /*Status register*/
            ret = (dma_stat_rq_pc & 0xf0);
            ret |= dma_stat >> 4;
            dma_stat &= ~0xf0;
            break;

        default:
            break;
    }

    dma_log("dma16_read(%08X) = %02X\n", addr, ret);

    return ret;
}

static void
dma16_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    int channel = ((addr >> 2) & 3) + 4;

    dma_log("dma16_write(%08X, %02X)\n", addr, val);

    addr >>= 1;

    dmaregs[1][addr & 0xf] = val;
    switch (addr & 0xf) {
        case 0:
        case 2:
        case 4:
        case 6: /*Address registers*/
            dma_wp[1] ^= 1;
            if (dma_ps2.is_ps2) {
                if (dma_wp[1])
                    dma[channel].ab = (dma[channel].ab & 0xffffff00 & dma_mask) | val;
                else
                    dma[channel].ab = (dma[channel].ab & 0xffff00ff & dma_mask) | (val << 8);
            } else {
                if (dma_wp[1])
                    dma[channel].ab = (dma[channel].ab & 0xfffffe00 & dma_mask) | (val << 1);
                else
                    dma[channel].ab = (dma[channel].ab & 0xfffe01ff & dma_mask) | (val << 9);
            }
            dma[channel].ac = dma[channel].ab;
            return;

        case 1:
        case 3:
        case 5:
        case 7: /*Count registers*/
            dma_wp[1] ^= 1;
            if (dma_wp[1])
                dma[channel].cb = (dma[channel].cb & 0xff00) | val;
            else
                dma[channel].cb = (dma[channel].cb & 0x00ff) | (val << 8);
            dma[channel].cc = dma[channel].cb;
            return;

        case 8: /*Control register*/
            return;

        case 9: /*Request register */
            channel = (val & 3) + 4;
            if (val & 4) {
                dma_stat_rq_pc |= (1 << channel);
                dma_block_transfer(channel);
            } else
                dma_stat_rq_pc &= ~(1 << channel);
            break;

        case 0xa: /*Mask*/
            channel = (val & 3);
            if (val & 4)
                dma_m |= (0x10 << channel);
            else
                dma_m &= ~(0x10 << channel);
            return;

        case 0xb: /*Mode*/
            channel           = (val & 3) + 4;
            dma[channel].mode = val;
            if (dma_ps2.is_ps2) {
                dma[channel].ps2_mode &= ~0x1c;
                if (val & 0x20)
                    dma[channel].ps2_mode |= 0x10;
                if ((val & 0xc) == 8)
                    dma[channel].ps2_mode |= 4;
                else if ((val & 0xc) == 4)
                    dma[channel].ps2_mode |= 0xc;
            }
            return;

        case 0xc: /*Clear FF*/
            dma_wp[1] = 0;
            return;

        case 0xd: /*Master clear*/
            dma_wp[1] = 0;
            dma_m |= 0xf0;
            dma_stat_rq_pc &= ~0xf0;
            return;

        case 0xe: /*Clear mask*/
            dma_m &= 0x0f;
            return;

        case 0xf: /*Mask write*/
            dma_m = (dma_m & 0x0f) | ((val & 0xf) << 4);
            return;

        default:
            break;
    }
}

#define CHANNELS               \
    {                          \
        8, 2, 3, 1, 8, 8, 8, 0 \
    }

static void
dma_page_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    uint8_t convert[8] = CHANNELS;

    dma_log("DMA: [W] %04X = %02X\n", addr, val);

#ifdef USE_DYNAREC
    if ((addr == 0x84) && cpu_use_dynarec)
        update_tsc();
#endif

    addr &= 0x0f;
    dmaregs[2][addr] = val;

    if (machines[machine].init == machine_xt_ibm5550_init) {
        if (addr >= 4)
            addr = 8;
    } else {
        if (addr >= 8)
            addr = convert[addr & 0x07] | 4;
        else
            addr = convert[addr & 0x07];
    }

    if (addr < 8) {
        dma[addr].page_l = val;

        if (addr > 4) {
            dma[addr].page = val & 0xfe;
            dma[addr].ab   = (dma[addr].ab & 0xff01ffff & dma_mask) | (dma[addr].page << 16);
            dma[addr].ac   = (dma[addr].ac & 0xff01ffff & dma_mask) | (dma[addr].page << 16);
        } else {
            dma[addr].page = dma_at ? val : val & 0xf;
            dma[addr].ab   = (dma[addr].ab & 0xff00ffff & dma_mask) | (dma[addr].page << 16);
            dma[addr].ac   = (dma[addr].ac & 0xff00ffff & dma_mask) | (dma[addr].page << 16);
        }
    }
}

static uint8_t
dma_page_read(uint16_t addr, UNUSED(void *priv))
{
    uint8_t convert[8] = CHANNELS;
    uint8_t ret        = 0xff;

    if (((addr & 0xfffc) == 0x80) && (CS == 0xf000) &&
        ((cpu_state.pc & 0xfffffff8) == 0x00007278) &&
        (machines[machine].init == machine_at_wd76c10_init))  switch (addr) {
        /* The Amstrad MegaPC Quadtel BIOS times a sequence of:
               mov ax,di
               div bx
           And expects this value to be at least 0x06e0 for 20 MHz,
           and at least 0x0898 for 25 MHz, everything below 0x06e0
           is assumed to be 16 MHz. Given that for some reason, this
           does not occur on 86Box, we have to work around it here,
           we return 0x0580 for 16 MHz, because it logically follows
           in the sequence (0x06e0 = 0x0898 * (20 / 25), and
           0x0580 = 0x06e0 * (16 / 20)). */
        case 0x0081:
            if (cpu_busspeed >= 25000000)
                ret = 0x98;
            else if (cpu_busspeed >= 20000000)
                ret = 0xe0;
            else
                ret = 0x80;
            break;
        case 0x0082:
            if (cpu_busspeed >= 25000000)
                ret = 0x08;
            else if (cpu_busspeed >= 20000000)
                ret = 0x06;
            else
                ret = 0x05;
            break;
    } else {
        addr &= 0x0f;
        ret = dmaregs[2][addr];

        if (addr >= 8)
            addr = convert[addr & 0x07] | 4;
        else
            addr = convert[addr & 0x07];

        if (addr < 8)
            ret = dma[addr].page_l;
    }

    dma_log("DMA: [R] %04X = %02X\n", addr, ret);

    return ret;
}

static void
dma_high_page_write(uint16_t addr, uint8_t val, UNUSED(void *priv))
{
    uint8_t convert[8] = CHANNELS;

    addr &= 0x0f;

    if (addr >= 8)
        addr = convert[addr & 0x07] | 4;
    else
        addr = convert[addr & 0x07];

    if (addr < 8) {
        dma[addr].page_h = val;

        dma[addr].ab = ((dma[addr].ab & 0xffffff) | (dma[addr].page << 24)) & dma_mask;
        dma[addr].ac = ((dma[addr].ac & 0xffffff) | (dma[addr].page << 24)) & dma_mask;
    }
}

static uint8_t
dma_high_page_read(uint16_t addr, UNUSED(void *priv))
{
    uint8_t convert[8] = CHANNELS;
    uint8_t ret        = 0xff;

    addr &= 0x0f;

    if (addr >= 8)
        addr = convert[addr & 0x07] | 4;
    else
        addr = convert[addr & 0x07];

    if (addr < 8)
        ret = dma[addr].page_h;

    return ret;
}

void
dma_set_params(uint8_t advanced, uint32_t mask)
{
    dma_advanced = advanced;
    dma_mask     = mask;
}

void
dma_set_mask(uint32_t mask)
{
    dma_mask = mask;

    for (uint8_t i = 0; i < 8; i++) {
        dma[i].ab &= mask;
        dma[i].ac &= mask;
    }
}

void
dma_set_at(uint8_t at)
{
    dma_at = at;
}

void
dma_reset(void)
{
    int c;

    dma_wp[0] = dma_wp[1] = 0;
    dma_m                 = 0;

    dma_e = 0xff;

    for (c = 0; c < 16; c++)
        dmaregs[0][c] = dmaregs[1][c] = 0;

    for (c = 0; c < 8; c++) {
        memset(&(dma[c]), 0x00, sizeof(dma_t));
        dma[c].size          = (c & 4) ? 1 : 0;
        dma[c].transfer_mode = (c & 4) ? 0x0202 : 0x0101;
    }

    dma_stat          = 0x00;
    dma_stat_rq       = 0x00;
    dma_stat_rq_pc    = 0x00;
    dma_stat_adv_pend = 0x00;
    dma_req_is_soft   = 0;
    dma_advanced      = 0;

    memset(dma_buffer, 0x00, sizeof(dma_buffer));
    memset(dma16_buffer, 0x00, sizeof(dma16_buffer));

    dma_remove_sg();
    dma_sg_base = 0x0400;

    dma_mask = 0x00ffffff;

    dma_at = is286;
}

void
dma_remove_sg(void)
{
    io_removehandler(dma_sg_base + 0x0a, 0x01,
                     dma_sg_int_status_read, NULL, NULL,
                     NULL, NULL, NULL,
                     NULL);

    for (uint8_t i = 0; i < 8; i++) {
        io_removehandler(dma_sg_base + 0x10 + i, 0x01,
                         dma_sg_read, dma_sg_readw, dma_sg_readl,
                         dma_sg_write, dma_sg_writew, dma_sg_writel,
                         &dma[i]);
        io_removehandler(dma_sg_base + 0x18 + i, 0x01,
                         dma_sg_read, dma_sg_readw, dma_sg_readl,
                         dma_sg_write, dma_sg_writew, dma_sg_writel,
                         &dma[i]);
        io_removehandler(dma_sg_base + 0x20 + i * 4, 0x04,
                         dma_sg_read, dma_sg_readw, dma_sg_readl,
                         dma_sg_write, dma_sg_writew, dma_sg_writel,
                         &dma[i]);
    }
}

void
dma_set_sg_base(uint8_t sg_base)
{
    dma_sg_base = sg_base << 8;

    io_sethandler(dma_sg_base + 0x0a, 0x01,
                  dma_sg_int_status_read, NULL, NULL,
                  NULL, NULL, NULL,
                  NULL);

    for (uint8_t i = 0; i < 8; i++) {
        io_sethandler(dma_sg_base + 0x10 + i, 0x01,
                      dma_sg_read, dma_sg_readw, dma_sg_readl,
                      dma_sg_write, dma_sg_writew, dma_sg_writel,
                      &dma[i]);
        io_sethandler(dma_sg_base + 0x18 + i, 0x01,
                      dma_sg_read, dma_sg_readw, dma_sg_readl,
                      dma_sg_write, dma_sg_writew, dma_sg_writel,
                      &dma[i]);
        io_sethandler(dma_sg_base + 0x20 + i * 4, 0x04,
                      dma_sg_read, dma_sg_readw, dma_sg_readl,
                      dma_sg_write, dma_sg_writew, dma_sg_writel,
                      &dma[i]);
    }
}

void
dma_ext_mode_init(void)
{
    io_sethandler(0x040b, 0x01,
                  NULL, NULL, NULL, dma_ext_mode_write, NULL, NULL, NULL);
    io_sethandler(0x04d6, 0x01,
                  NULL, NULL, NULL, dma_ext_mode_write, NULL, NULL, NULL);
}

void
dma_high_page_init(void)
{
    io_sethandler(0x0480, 8,
                  dma_high_page_read, NULL, NULL, dma_high_page_write, NULL, NULL, NULL);
}

void
dma_init(void)
{
    dma_reset();

    io_sethandler(0x0000, 16,
                  dma_read, NULL, NULL, dma_write, NULL, NULL, NULL);
    io_sethandler(0x0080, 8,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    dma_ps2.is_ps2 = 0;
}

void
dma16_init(void)
{
    dma_reset();

    io_sethandler(0x00C0, 32,
                  dma16_read, NULL, NULL, dma16_write, NULL, NULL, NULL);
    io_sethandler(0x0088, 8,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
}

void
dma_alias_set(void)
{
    io_sethandler(0x0090, 2,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_sethandler(0x0093, 13,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
}

void
dma_alias_set_piix(void)
{
    io_sethandler(0x0090, 1,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_sethandler(0x0094, 3,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_sethandler(0x0098, 1,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_sethandler(0x009C, 3,
                  dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
}

void
dma_alias_remove(void)
{
    io_removehandler(0x0090, 2,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_removehandler(0x0093, 13,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
}

void
dma_alias_remove_piix(void)
{
    io_removehandler(0x0090, 1,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_removehandler(0x0094, 3,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_removehandler(0x0098, 1,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
    io_removehandler(0x009C, 3,
                     dma_page_read, NULL, NULL, dma_page_write, NULL, NULL, NULL);
}

void
ps2_dma_init(void)
{
    dma_reset();

    io_sethandler(0x0018, 1,
                  dma_ps2_read, NULL, NULL, dma_ps2_write, NULL, NULL, NULL);
    io_sethandler(0x001a, 1,
                  dma_ps2_read, NULL, NULL, dma_ps2_write, NULL, NULL, NULL);
    dma_ps2.is_ps2 = 1;
}

extern void dma_bm_read(uint32_t PhysAddress, uint8_t *DataRead, uint32_t TotalSize, int TransferSize);
extern void dma_bm_write(uint32_t PhysAddress, const uint8_t *DataWrite, uint32_t TotalSize, int TransferSize);

static int
dma_sg(uint8_t *data, int transfer_length, int out, void *priv)
{
    dma_t *dev = (dma_t *) priv;
#ifdef ENABLE_DMA_LOG
    char *sop;
#endif

    int force_end = 0;
    int buffer_pos = 0;

#ifdef ENABLE_DMA_LOG
    sop = out ? "Read" : "Writ";
#endif

    if (!(dev->sg_status & 1))
        return 2; /*S/G disabled*/

    dma_log("DMA S/G %s: %i bytes\n", out ? "write" : "read", transfer_length);

    while (1) {
        if (dev->count <= transfer_length) {
            dma_log("%sing %i bytes to %08X\n", sop, dev->count, dev->addr);
            if (out)
                dma_bm_read(dev->addr, (uint8_t *) (data + buffer_pos), dev->count, 4);
            else
                dma_bm_write(dev->addr, (uint8_t *) (data + buffer_pos), dev->count, 4);
            transfer_length -= dev->count;
            buffer_pos += dev->count;
        } else {
            dma_log("%sing %i bytes to %08X\n", sop, transfer_length, dev->addr);
            if (out)
                dma_bm_read(dev->addr, (uint8_t *) (data + buffer_pos), transfer_length, 4);
            else
                dma_bm_write(dev->addr, (uint8_t *) (data + buffer_pos), transfer_length, 4);
            /* Increase addr and decrease count so that resumed transfers do not mess up. */
            dev->addr += transfer_length;
            dev->count -= transfer_length;
            transfer_length = 0;
            force_end       = 1;
        }

        if (force_end) {
            dma_log("Total transfer length smaller than sum of all blocks, partial block\n");
            return 1; /* This block has exhausted the data to transfer and it was smaller than the count, break. */
        } else {
            if (!transfer_length && !dev->eot) {
                dma_log("Total transfer length smaller than sum of all blocks, full block\n");
                return 1; /* We have exhausted the data to transfer but there's more blocks left, break. */
            } else if (transfer_length && dev->eot) {
                dma_log("Total transfer length greater than sum of all blocks\n");
                return 4; /* There is data left to transfer but we have reached EOT - return with error. */
            } else if (dev->eot) {
                dma_log("Regular EOT\n");
                return 5; /* We have regularly reached EOT - clear status and break. */
            } else {
                /* We have more to transfer and there are blocks left, get next block. */
                dma_sg_next_addr(dev);
            }
        }
    }
}

uint8_t
_dma_read(uint32_t addr, dma_t *dma_c)
{
    uint8_t temp = 0;

    if (dma_advanced) {
        if (dma_c->sg_status & 1)
            dma_c->sg_status = (dma_c->sg_status & 0x0f) | (dma_sg(&temp, 1, 1, dma_c) << 4);
        else
            dma_bm_read(addr, &temp, 1, dma_transfer_size(dma_c));
    } else
        temp = mem_readb_phys(addr);

    return temp;
}

static uint16_t
_dma_readw(uint32_t addr, dma_t *dma_c)
{
    uint16_t temp = 0;

    if (dma_advanced) {
        if (dma_c->sg_status & 1)
            dma_c->sg_status = (dma_c->sg_status & 0x0f) | (dma_sg((uint8_t *) &temp, 2, 1, dma_c) << 4);
        else
            dma_bm_read(addr, (uint8_t *) &temp, 2, dma_transfer_size(dma_c));
    } else
        temp = _dma_read(addr, dma_c) | (_dma_read(addr + 1, dma_c) << 8);

    return temp;
}

static void
_dma_write(uint32_t addr, uint8_t val, dma_t *dma_c)
{
    if (dma_advanced) {
        if (dma_c->sg_status & 1)
            dma_c->sg_status = (dma_c->sg_status & 0x0f) | (dma_sg(&val, 1, 0, dma_c) << 4);
        else
            dma_bm_write(addr, &val, 1, dma_transfer_size(dma_c));
    } else {
        mem_writeb_phys(addr, val);
        if (dma_at)
            mem_invalidate_range(addr, addr);
    }
}

static void
_dma_writew(uint32_t addr, uint16_t val, dma_t *dma_c)
{
    if (dma_advanced) {
        if (dma_c->sg_status & 1)
            dma_c->sg_status = (dma_c->sg_status & 0x0f) | (dma_sg((uint8_t *) &val, 2, 0, dma_c) << 4);
        else
            dma_bm_write(addr, (uint8_t *) &val, 2, dma_transfer_size(dma_c));
    } else {
        _dma_write(addr, val & 0xff, dma_c);
        _dma_write(addr + 1, val >> 8, dma_c);
    }
}

static void
dma_retreat(dma_t *dma_c)
{
    int as = dma_c->transfer_mode >> 8;

    if (dma->sg_status & 1) {
        dma_c->ac = (dma_c->ac - as) & dma_mask;

        dma_c->page = dma_c->page_l = (dma_c->ac >> 16) & 0xff;
        dma_c->page_h               = (dma_c->ac >> 24) & 0xff;
    } else if (as == 2)
        dma_c->ac = ((dma_c->ac & 0xfffe0000) & dma_mask) | ((dma_c->ac - as) & 0x1ffff);
    else
        dma_c->ac = ((dma_c->ac & 0xffff0000) & dma_mask) | ((dma_c->ac - as) & 0xffff);
}

void
dma_advance(dma_t *dma_c)
{
    int as = dma_c->transfer_mode >> 8;

    if (dma->sg_status & 1) {
        dma_c->ac = (dma_c->ac + as) & dma_mask;

        dma_c->page = dma_c->page_l = (dma_c->ac >> 16) & 0xff;
        dma_c->page_h               = (dma_c->ac >> 24) & 0xff;
    } else if (as == 2)
        dma_c->ac = ((dma_c->ac & 0xfffe0000) & dma_mask) | ((dma_c->ac + as) & 0x1ffff);
    else
        dma_c->ac = ((dma_c->ac & 0xffff0000) & dma_mask) | ((dma_c->ac + as) & 0xffff);
}

int
dma_channel_readable(int channel)
{
    dma_t   *dma_c = &dma[channel];
    int      ret = 1;

    if (channel < 4) {
        if (dma_command[0] & 0x04)
            ret = 0;
    } else {
        if (dma_command[1] & 0x04)
            ret = 0;
    }

    if (!(dma_e & (1 << channel)))
        ret = 0;
    if ((dma_m & (1 << channel)) && !dma_req_is_soft)
        ret = 0;
    if ((dma_c->mode & 0xC) != 8)
        ret = 0;

    return ret;
}

int
dma_channel_read_only(int channel)
{
    dma_t   *dma_c = &dma[channel];
    uint16_t temp;

    if (channel < 4) {
        if (dma_command[0] & 0x04)
            return (DMA_NODATA);
    } else {
        if (dma_command[1] & 0x04)
            return (DMA_NODATA);
    }

    if (!(dma_e & (1 << channel)))
        return (DMA_NODATA);
    if ((dma_m & (1 << channel)) && !dma_req_is_soft)
        return (DMA_NODATA);
    if ((dma_c->mode & 0xC) != 8)
        return (DMA_NODATA);

    dma_channel_advance(channel);

    if (!dma_at && !channel)
        is_nec ? refreshread_vx0() : refreshread();

    if (!dma_c->size) {
        temp = _dma_read(dma_c->ac, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac--;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac - 1) & 0xffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac++;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac + 1) & 0xffff);
        }
    } else {
        temp = _dma_readw(dma_c->ac, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac -= 2;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac - 2) & 0x1ffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac += 2;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac + 2) & 0x1ffff);
        }
    }

    dma_stat_rq |= (1 << channel);

    dma_stat_adv_pend |= (1 << channel);

    return temp;
}

int
dma_channel_advance(int channel)
{
    dma_t   *dma_c = &dma[channel];
    int      tc = 0;

    if (dma_stat_adv_pend & (1 << channel)) {
        dma_c->cc--;
        if (dma_c->cc < 0) {
            if (dma_advanced && (dma_c->sg_status & 1) && !(dma_c->sg_status & 6))
                dma_sg_next_addr(dma_c);
            else {
                tc = 1;
                if (dma_c->mode & 0x10) { /*Auto-init*/
                    dma_c->cc = dma_c->cb;
                    dma_c->ac = dma_c->ab;
                } else
                    dma_m |= (1 << channel);
                dma_stat |= (1 << channel);
            }
        }

        if (tc) {
            if (dma_advanced && (dma_c->sg_status & 1) && ((dma_c->sg_command & 0xc0) == 0x40)) {
                picint(1 << 13);
                dma_c->sg_status |= 8;
            }
        }

        dma_stat_adv_pend &= ~(1 << channel);
    }

    return tc;
}

int
dma_channel_read(int channel)
{
    dma_t   *dma_c = &dma[channel];
    uint16_t temp;
    int      tc = 0;

    if (channel < 4) {
        if (dma_command[0] & 0x04)
            return (DMA_NODATA);
    } else {
        if (dma_command[1] & 0x04)
            return (DMA_NODATA);
    }

    if (!(dma_e & (1 << channel)))
        return (DMA_NODATA);
    if ((dma_m & (1 << channel)) && !dma_req_is_soft)
        return (DMA_NODATA);
    if ((dma_c->mode & 0xC) != 8)
        return (DMA_NODATA);

    if (dma_stat_adv_pend & (1 << channel))
        dma_channel_advance(channel);

    if (!dma_at && !channel)
        is_nec ? refreshread_vx0() : refreshread();

    if (!dma_c->size) {
        temp = _dma_read(dma_c->ac, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac--;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac - 1) & 0xffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac++;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac + 1) & 0xffff);
        }
    } else {
        temp = _dma_readw(dma_c->ac, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac -= 2;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac - 2) & 0x1ffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac += 2;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac + 2) & 0x1ffff);
        }
    }

    dma_stat_rq |= (1 << channel);

    dma_c->cc--;
    if (dma_c->cc < 0) {
        if (dma_advanced && (dma_c->sg_status & 1) && !(dma_c->sg_status & 6))
            dma_sg_next_addr(dma_c);
        else {
            tc = 1;
            if (dma_c->mode & 0x10) { /*Auto-init*/
                dma_c->cc = dma_c->cb;
                dma_c->ac = dma_c->ab;
            } else
                dma_m |= (1 << channel);
            dma_stat |= (1 << channel);
        }
    }

    if (tc) {
        if (dma_advanced && (dma_c->sg_status & 1) && ((dma_c->sg_command & 0xc0) == 0x40)) {
            picint(1 << 13);
            dma_c->sg_status |= 8;
        }

        return (temp | DMA_OVER);
    }

    return temp;
}

int
dma_channel_write(int channel, uint16_t val)
{
    dma_t *dma_c = &dma[channel];

    if (channel < 4) {
        if (dma_command[0] & 0x04)
            return (DMA_NODATA);
    } else {
        if (dma_command[1] & 0x04)
            return (DMA_NODATA);
    }

    if (!(dma_e & (1 << channel)))
        return (DMA_NODATA);
    if ((dma_m & (1 << channel)) && !dma_req_is_soft)
        return (DMA_NODATA);
    if ((dma_c->mode & 0xC) != 4)
        return (DMA_NODATA);

    if (!dma_c->size) {
        _dma_write(dma_c->ac, val & 0xff, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac--;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac - 1) & 0xffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac++;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xffff0000 & dma_mask) | ((dma_c->ac + 1) & 0xffff);
        }
    } else {
        _dma_writew(dma_c->ac, val, dma_c);

        if (dma_c->mode & 0x20) {
            if (dma_ps2.is_ps2)
                dma_c->ac -= 2;
            else if (dma_advanced)
                dma_retreat(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac - 2) & 0x1ffff);
            dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac - 2) & 0x1ffff);
        } else {
            if (dma_ps2.is_ps2)
                dma_c->ac += 2;
            else if (dma_advanced)
                dma_advance(dma_c);
            else
                dma_c->ac = (dma_c->ac & 0xfffe0000 & dma_mask) | ((dma_c->ac + 2) & 0x1ffff);
        }
    }

    dma_stat_rq |= (1 << channel);

    dma_stat_adv_pend &= ~(1 << channel);

    dma_c->cc--;
    if (dma_c->cc < 0) {
        if (dma_advanced && (dma_c->sg_status & 1) && !(dma_c->sg_status & 6))
            dma_sg_next_addr(dma_c);
        else {
            if (dma_c->mode & 0x10) { /*Auto-init*/
                dma_c->cc = dma_c->cb;
                dma_c->ac = dma_c->ab;
            } else
                dma_m |= (1 << channel);
            dma_stat |= (1 << channel);
        }
    }

    if (dma_m & (1 << channel)) {
        if (dma_advanced && (dma_c->sg_status & 1) && ((dma_c->sg_command & 0xc0) == 0x40)) {
            picint(1 << 13);
            dma_c->sg_status |= 8;
        }

        return DMA_OVER;
    }

    return 0;
}

static void
dma_ps2_run(int channel)
{
    dma_t *dma_c = &dma[channel];

    switch (dma_c->ps2_mode & DMA_PS2_XFER_MASK) {
        case DMA_PS2_XFER_MEM_TO_IO:
            do {
                if (!dma_c->size) {
                    uint8_t temp = _dma_read(dma_c->ac, dma_c);

                    outb(dma_c->io_addr, temp);

                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac--;
                    else
                        dma_c->ac++;
                } else {
                    uint16_t temp = _dma_readw(dma_c->ac, dma_c);

                    outw(dma_c->io_addr, temp);

                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac -= 2;
                    else
                        dma_c->ac += 2;
                }

                dma_stat_rq |= (1 << channel);
                dma_c->cc--;
            } while (dma_c->cc > 0);

            dma_stat |= (1 << channel);
            break;

        case DMA_PS2_XFER_IO_TO_MEM:
            do {
                if (!dma_c->size) {
                    uint8_t temp = inb(dma_c->io_addr);

                    _dma_write(dma_c->ac, temp, dma_c);

                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac--;
                    else
                        dma_c->ac++;
                } else {
                    uint16_t temp = inw(dma_c->io_addr);

                    _dma_writew(dma_c->ac, temp, dma_c);

                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac -= 2;
                    else
                        dma_c->ac += 2;
                }

                dma_stat_rq |= (1 << channel);
                dma_c->cc--;
            } while (dma_c->cc > 0);

            ps2_cache_clean();
            dma_stat |= (1 << channel);
            break;

        default: /*Memory verify*/
            do {
                if (!dma_c->size) {
                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac--;
                    else
                        dma_c->ac++;
                } else {
                    if (dma_c->ps2_mode & DMA_PS2_DEC2)
                        dma_c->ac -= 2;
                    else
                        dma_c->ac += 2;
                }

                dma_stat_rq |= (1 << channel);
                dma->cc--;
            } while (dma->cc > 0);

            dma_stat |= (1 << channel);
            break;
    }
}

int
dma_mode(int channel)
{
    return (dma[channel].mode);
}

/* DMA Bus Master Page Read/Write */
void
dma_bm_read(uint32_t PhysAddress, uint8_t *DataRead, uint32_t TotalSize, int TransferSize)
{
    uint32_t n;
    uint32_t n2;
    uint8_t  bytes[4] = { 0, 0, 0, 0 };

    n  = TotalSize & ~(TransferSize - 1);
    n2 = TotalSize - n;

    /* Do the divisible block, if there is one. */
    if (n) {
        for (uint32_t i = 0; i < n; i += TransferSize)
            mem_read_phys((void *) &(DataRead[i]), PhysAddress + i, TransferSize);
    }

    /* Do the non-divisible block, if there is one. */
    if (n2) {
        mem_read_phys((void *) bytes, PhysAddress + n, TransferSize);
        memcpy((void *) &(DataRead[n]), bytes, n2);
    }
}

void
dma_bm_write(uint32_t PhysAddress, const uint8_t *DataWrite, uint32_t TotalSize, int TransferSize)
{
    uint32_t n;
    uint32_t n2;
    uint8_t  bytes[4] = { 0, 0, 0, 0 };

    n  = TotalSize & ~(TransferSize - 1);
    n2 = TotalSize - n;

    /* Do the divisible block, if there is one. */
    if (n) {
        for (uint32_t i = 0; i < n; i += TransferSize)
            mem_write_phys((void *) &(DataWrite[i]), PhysAddress + i, TransferSize);
    }

    /* Do the non-divisible block, if there is one. */
    if (n2) {
        mem_read_phys((void *) bytes, PhysAddress + n, TransferSize);
        memcpy(bytes, (void *) &(DataWrite[n]), n2);
        mem_write_phys((void *) bytes, PhysAddress + n, TransferSize);
    }

    if (dma_at)
        mem_invalidate_range(PhysAddress, PhysAddress + TotalSize - 1);
}
