/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2002-2021  The Bochs Project
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
//
/////////////////////////////////////////////////////////////////////////

// Define BX_PLUGGABLE in files that can be compiled into plugins.  For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE

#include "iodev.h"
#include "dma.h"

#include "bx_debug/debug.h"

#define LOG_THIS theDmaDevice->

enum {
  DMA_MODE_DEMAND = 0,
  DMA_MODE_SINGLE = 1,
  DMA_MODE_BLOCK  = 2,
  DMA_MODE_CASCADE = 3
};

bx_dma_c *theDmaDevice = NULL;

PLUGIN_ENTRY_FOR_MODULE(dma)
{
  if (mode == PLUGIN_INIT) {
    theDmaDevice = new bx_dma_c ();
    bx_devices.pluginDmaDevice = theDmaDevice;
    BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theDmaDevice, BX_PLUGIN_DMA);
  } else if (mode == PLUGIN_FINI) {
    delete theDmaDevice;
  } else if (mode == PLUGIN_PROBE) {
    return (int)PLUGTYPE_CORE;
  }
  return 0; // Success
}

bx_dma_c::bx_dma_c()
{
  put("DMA");
  memset(&s, 0, sizeof(s));
}

bx_dma_c::~bx_dma_c()
{
  SIM->get_bochs_root()->remove("dma");
  BX_DEBUG(("Exit"));
}

bool bx_dma_c::registerDMA8Channel(unsigned channel,
    Bit16u (* dmaRead)(Bit8u *data_byte, Bit16u maxlen),
    Bit16u (* dmaWrite)(Bit8u *data_byte, Bit16u maxlen),
    const char *name)
{
  if (channel > 3) {
    BX_PANIC(("registerDMA8Channel: invalid channel number(%u).", channel));
    return false; // Fail
  }
  if (BX_DMA_THIS s[0].chan[channel].used) {
    BX_PANIC(("registerDMA8Channel: channel(%u) already in use.", channel));
    return false; // Fail
  }
  BX_INFO(("channel %u used by %s", channel, name));
  BX_DMA_THIS h[channel].dmaRead8  = dmaRead;
  BX_DMA_THIS h[channel].dmaWrite8 = dmaWrite;
  BX_DMA_THIS s[0].chan[channel].used = true;
  return true; // OK
}

bool bx_dma_c::registerDMA16Channel(unsigned channel,
    Bit16u (* dmaRead)(Bit16u *data_word, Bit16u maxlen),
    Bit16u (* dmaWrite)(Bit16u *data_word, Bit16u maxlen),
    const char *name)
{
  if ((channel < 4) || (channel > 7)) {
    BX_PANIC(("registerDMA16Channel: invalid channel number(%u).", channel));
    return false; // Fail
  }
  if (BX_DMA_THIS s[1].chan[channel & 0x03].used) {
    BX_PANIC(("registerDMA16Channel: channel(%u) already in use.", channel));
    return false; // Fail
  }
  BX_INFO(("channel %u used by %s", channel, name));
  channel &= 0x03;
  BX_DMA_THIS h[channel].dmaRead16  = dmaRead;
  BX_DMA_THIS h[channel].dmaWrite16 = dmaWrite;
  BX_DMA_THIS s[1].chan[channel].used = true;
  return true; // OK
}

bool bx_dma_c::unregisterDMAChannel(unsigned channel)
{
  BX_DMA_THIS s[(channel > 3) ? 1 : 0].chan[channel & 0x03].used = false;
  BX_INFO(("channel %u no longer used", channel));
  return true;
}

bool bx_dma_c::get_TC(void)
{
  return BX_DMA_THIS TC;
}

void bx_dma_c::init(void)
{
  unsigned c, i, j;

  /* 8237 DMA controller */

  for (i=0; i < 2; i++) {
    for (j=0; j < 4; j++) {
      BX_DMA_THIS s[i].DRQ[j] = 0;
      BX_DMA_THIS s[i].DACK[j] = 0;
    }
  }
  BX_DMA_THIS HLDA = 0;
  BX_DMA_THIS TC = 0;

  // 0000..000F
  for (i=0x0000; i<=0x000F; i++) {
    DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
    DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
  }

  // 00080..008F
  for (i=0x0080; i<=0x008F; i++) {
    DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
    DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
  }

  // 000C0..00DE
  for (i=0x00C0; i<=0x00DE; i+=2) {
    DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1);
    DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3);
  }

  for (i=0; i<2; i++) {
    for (c=0; c<4; c++) {
      BX_DMA_THIS s[i].chan[c].mode.mode_type = 0;         // demand mode
      BX_DMA_THIS s[i].chan[c].mode.address_decrement = 0; // address increment
      BX_DMA_THIS s[i].chan[c].mode.autoinit_enable = 0;   // autoinit disable
      BX_DMA_THIS s[i].chan[c].mode.transfer_type = 0;     // verify
      BX_DMA_THIS s[i].chan[c].base_address = 0;
      BX_DMA_THIS s[i].chan[c].current_address = 0;
      BX_DMA_THIS s[i].chan[c].base_count = 0;
      BX_DMA_THIS s[i].chan[c].current_count = 0;
      BX_DMA_THIS s[i].chan[c].page_reg = 0;
      BX_DMA_THIS s[i].chan[c].used = false;
    }
  }
  memset(&BX_DMA_THIS ext_page_reg[0], 0, 16);
  BX_DMA_THIS s[1].chan[0].used = true; // cascade channel in use
  BX_INFO(("channel 4 used by cascade"));
#if BX_DEBUGGER
  // register device for the 'info device' command (calls debug_dump())
  bx_dbg_register_debug_info("dma", this);
#endif
}

void bx_dma_c::reset(unsigned type)
{
  reset_controller(0);
  reset_controller(1);
}

void bx_dma_c::reset_controller(unsigned num)
{
  BX_DMA_THIS s[num].mask[0] = 1;
  BX_DMA_THIS s[num].mask[1] = 1;
  BX_DMA_THIS s[num].mask[2] = 1;
  BX_DMA_THIS s[num].mask[3] = 1;
  BX_DMA_THIS s[num].ctrl_disabled = 0;
  BX_DMA_THIS s[num].command_reg = 0;
  BX_DMA_THIS s[num].status_reg = 0;
  BX_DMA_THIS s[num].flip_flop = 0;
}

void bx_dma_c::register_state(void)
{
  char name[6];
  bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "dma", "DMA State");
  for (unsigned i=0; i<2; i++) {
    sprintf(name, "%u", i);
    bx_list_c *ctrl = new bx_list_c(list, name);
    BXRS_PARAM_BOOL(ctrl, flip_flop, BX_DMA_THIS s[i].flip_flop);
    BXRS_HEX_PARAM_FIELD(ctrl, status_reg, BX_DMA_THIS s[i].status_reg);
    BXRS_HEX_PARAM_FIELD(ctrl, command_reg, BX_DMA_THIS s[i].command_reg);
    BXRS_PARAM_BOOL(ctrl, ctrl_disabled, BX_DMA_THIS s[i].ctrl_disabled);
    for (unsigned c=0; c<4; c++) {
      sprintf(name, "%u", c);
      bx_list_c *chan = new bx_list_c(ctrl, name);
      BXRS_PARAM_BOOL(chan, DRQ, BX_DMA_THIS s[i].DRQ[c]);
      BXRS_PARAM_BOOL(chan, DACK, BX_DMA_THIS s[i].DACK[c]);
      BXRS_PARAM_BOOL(chan, mask, BX_DMA_THIS s[i].mask[c]);
      BXRS_DEC_PARAM_FIELD(chan, mode_type, BX_DMA_THIS s[i].chan[c].mode.mode_type);
      BXRS_PARAM_BOOL(chan, address_decrement, BX_DMA_THIS s[i].chan[c].mode.address_decrement);
      BXRS_PARAM_BOOL(chan, autoinit_enable, BX_DMA_THIS s[i].chan[c].mode.autoinit_enable);
      BXRS_DEC_PARAM_FIELD(chan, transfer_type, BX_DMA_THIS s[i].chan[c].mode.transfer_type);
      BXRS_HEX_PARAM_FIELD(chan, base_address, BX_DMA_THIS s[i].chan[c].base_address);
      BXRS_HEX_PARAM_FIELD(chan, current_address, BX_DMA_THIS s[i].chan[c].current_address);
      BXRS_HEX_PARAM_FIELD(chan, base_count, BX_DMA_THIS s[i].chan[c].base_count);
      BXRS_HEX_PARAM_FIELD(chan, current_count, BX_DMA_THIS s[i].chan[c].current_count);
      BXRS_HEX_PARAM_FIELD(chan, page_reg, BX_DMA_THIS s[i].chan[c].page_reg);
    }
  }
  new bx_shadow_data_c(list, "ext_page", BX_DMA_THIS ext_page_reg, 16, 1);
}

// index to find channel from register number (only [0],[1],[2],[6] used)
Bit8u channelindex[7] = {2, 3, 1, 0, 0, 0, 0};

// static IO port read callback handler
// redirects to non-static class handler to avoid virtual functions
Bit32u bx_dma_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_DMA_SMF
  bx_dma_c *class_ptr = (bx_dma_c *) this_ptr;
  return class_ptr->read(address, io_len);
}

  /* 8237 DMA controller */
  Bit32u BX_CPP_AttrRegparmN(2)
bx_dma_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_DMA_SMF

  Bit8u retval;
  Bit8u channel;

  BX_DEBUG(("read addr=%04x", (unsigned) address));

#if BX_DMA_FLOPPY_IO < 1
  /* if we're not supporting DMA/floppy IO just return a bogus value */
  return(0xff);
#endif

  Bit8u ma_sl = (address >= 0xc0) ? 1 : 0;

  switch (address) {
    case 0x00: /* DMA-1 current address, channel 0 */
    case 0x02: /* DMA-1 current address, channel 1 */
    case 0x04: /* DMA-1 current address, channel 2 */
    case 0x06: /* DMA-1 current address, channel 3 */
    case 0xc0: /* DMA-2 current address, channel 0 */
    case 0xc4: /* DMA-2 current address, channel 1 */
    case 0xc8: /* DMA-2 current address, channel 2 */
    case 0xcc: /* DMA-2 current address, channel 3 */
      channel = (address >> (1 + ma_sl)) & 0x03;
      if (BX_DMA_THIS s[ma_sl].flip_flop==0) {
        BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
        return (BX_DMA_THIS s[ma_sl].chan[channel].current_address & 0xff);
      } else {
        BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
        return (BX_DMA_THIS s[ma_sl].chan[channel].current_address >> 8);
      }

    case 0x01: /* DMA-1 current count, channel 0 */
    case 0x03: /* DMA-1 current count, channel 1 */
    case 0x05: /* DMA-1 current count, channel 2 */
    case 0x07: /* DMA-1 current count, channel 3 */
    case 0xc2: /* DMA-2 current count, channel 0 */
    case 0xc6: /* DMA-2 current count, channel 1 */
    case 0xca: /* DMA-2 current count, channel 2 */
    case 0xce: /* DMA-2 current count, channel 3 */
      channel = (address >> (1 + ma_sl)) & 0x03;
      if (BX_DMA_THIS s[ma_sl].flip_flop==0) {
        BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
        return (BX_DMA_THIS s[ma_sl].chan[channel].current_count & 0xff);
      } else {
        BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
        return (BX_DMA_THIS s[ma_sl].chan[channel].current_count >> 8);
      }

    case 0x08: // DMA-1 Status Register
    case 0xd0: // DMA-2 Status Register
      // bit 7: 1 = channel 3 request
      // bit 6: 1 = channel 2 request
      // bit 5: 1 = channel 1 request
      // bit 4: 1 = channel 0 request
      // bit 3: 1 = channel 3 has reached terminal count
      // bit 2: 1 = channel 2 has reached terminal count
      // bit 1: 1 = channel 1 has reached terminal count
      // bit 0: 1 = channel 0 has reached terminal count
      // reading this register clears lower 4 bits (hold flags)
      retval = BX_DMA_THIS s[ma_sl].status_reg;
      BX_DMA_THIS s[ma_sl].status_reg &= 0xf0;
      return retval;

    case 0x0d: // DMA-1: temporary register
    case 0xda: // DMA-2: temporary register
      // only used for memory-to-memory transfers
      // write to 0x0d / 0xda clears temporary register
      BX_ERROR(("DMA-%d: read of temporary register always returns 0", ma_sl+1));
      return 0;

    case 0x0081: // DMA-1 page register, channel 2
    case 0x0082: // DMA-1 page register, channel 3
    case 0x0083: // DMA-1 page register, channel 1
    case 0x0087: // DMA-1 page register, channel 0
      channel = channelindex[address - 0x81];
      return BX_DMA_THIS s[0].chan[channel].page_reg;

    case 0x0089: // DMA-2 page register, channel 2
    case 0x008a: // DMA-2 page register, channel 3
    case 0x008b: // DMA-2 page register, channel 1
    case 0x008f: // DMA-2 page register, channel 0
      channel = channelindex[address - 0x89];
      return BX_DMA_THIS s[1].chan[channel].page_reg;

    case 0x0080:
    case 0x0084:
    case 0x0085:
    case 0x0086:
    case 0x0088:
    case 0x008c:
    case 0x008d:
    case 0x008e:
      BX_DEBUG(("read: extra page register 0x%04x (unused)", (unsigned) address));
      return BX_DMA_THIS ext_page_reg[address & 0x0f];

    case 0x0f: // DMA-1: undocumented: read all mask bits
    case 0xde: // DMA-2: undocumented: read all mask bits
      retval = (Bit8u)BX_DMA_THIS s[ma_sl].mask[0] |
               (BX_DMA_THIS s[ma_sl].mask[1] << 1) |
               (BX_DMA_THIS s[ma_sl].mask[2] << 2) |
               (BX_DMA_THIS s[ma_sl].mask[3] << 3);
      return (0xf0 | retval);

    default:
      BX_ERROR(("read: unsupported address=%04x", (unsigned) address));
      return 0;
  }
}


// static IO port write callback handler
// redirects to non-static class handler to avoid virtual functions

void bx_dma_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_DMA_SMF
  bx_dma_c *class_ptr = (bx_dma_c *) this_ptr;

  class_ptr->write(address, value, io_len);
}


  /* 8237 DMA controller */
  void BX_CPP_AttrRegparmN(3)
bx_dma_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_DMA_SMF
  Bit8u set_mask_bit;
  Bit8u channel;

  if (io_len > 1) {
    if ((io_len == 2) && (address == 0x0b)) {
#if BX_USE_DMA_SMF
      BX_DMA_THIS write_handler(NULL, address,   value & 0xff, 1);
      BX_DMA_THIS write_handler(NULL, address+1, value >> 8,   1);
#else
      BX_DMA_THIS write(address,   value & 0xff, 1);
      BX_DMA_THIS write(address+1, value >> 8,   1);
#endif
      return;
    }

    BX_ERROR(("io write to address %08x, len=%u",
             (unsigned) address, (unsigned) io_len));
    return;
  }

  BX_DEBUG(("write: address=%04x value=%02x",
      (unsigned) address, (unsigned) value));

#if BX_DMA_FLOPPY_IO < 1
  /* if we're not supporting DMA/floppy IO just return */
  return;
#endif

  Bit8u ma_sl = (address >= 0xc0) ? 1 : 0;

  switch (address) {
    case 0x00:
    case 0x02:
    case 0x04:
    case 0x06:
    case 0xc0:
    case 0xc4:
    case 0xc8:
    case 0xcc:
      channel = (address >> (1 + ma_sl)) & 0x03;
      BX_DEBUG(("  DMA-%d base and current address, channel %d", ma_sl+1, channel));
      if (BX_DMA_THIS s[ma_sl].flip_flop==0) { /* 1st byte */
        BX_DMA_THIS s[ma_sl].chan[channel].base_address = value;
        BX_DMA_THIS s[ma_sl].chan[channel].current_address = value;
      } else { /* 2nd byte */
        BX_DMA_THIS s[ma_sl].chan[channel].base_address |= (value << 8);
        BX_DMA_THIS s[ma_sl].chan[channel].current_address |= (value << 8);
        BX_DEBUG(("    base = %04x", BX_DMA_THIS s[ma_sl].chan[channel].base_address));
        BX_DEBUG(("    curr = %04x", BX_DMA_THIS s[ma_sl].chan[channel].current_address));
      }
      BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
      break;

    case 0x01:
    case 0x03:
    case 0x05:
    case 0x07:
    case 0xc2:
    case 0xc6:
    case 0xca:
    case 0xce:
      channel = (address >> (1 + ma_sl)) & 0x03;
      BX_DEBUG(("  DMA-%d base and current count, channel %d", ma_sl+1, channel));
      if (BX_DMA_THIS s[ma_sl].flip_flop==0) { /* 1st byte */
        BX_DMA_THIS s[ma_sl].chan[channel].base_count = value;
        BX_DMA_THIS s[ma_sl].chan[channel].current_count = value;
      } else { /* 2nd byte */
        BX_DMA_THIS s[ma_sl].chan[channel].base_count |= (value << 8);
        BX_DMA_THIS s[ma_sl].chan[channel].current_count |= (value << 8);
        BX_DEBUG(("    base = %04x", BX_DMA_THIS s[ma_sl].chan[channel].base_count));
        BX_DEBUG(("    curr = %04x", BX_DMA_THIS s[ma_sl].chan[channel].current_count));
      }
      BX_DMA_THIS s[ma_sl].flip_flop = !BX_DMA_THIS s[ma_sl].flip_flop;
      break;

    case 0x08: /* DMA-1: command register */
    case 0xd0: /* DMA-2: command register */
      if ((value & 0xfb) != 0x00)
        BX_ERROR(("write to command register: value 0x%02x not supported",
          (unsigned) value));
      BX_DMA_THIS s[ma_sl].command_reg = value;
      BX_DMA_THIS s[ma_sl].ctrl_disabled = (value >> 2) & 0x01;
      control_HRQ(ma_sl);
      break;

    case 0x09: // DMA-1: request register
    case 0xd2: // DMA-2: request register
      channel = value & 0x03;
      // note: write to 0x0d / 0xda clears this register
      if (value & 0x04) {
        // set request bit
        BX_DMA_THIS s[ma_sl].status_reg |= (1 << (channel+4));
        BX_DEBUG(("DMA-%d: set request bit for channel %u", ma_sl+1, channel));
      } else {
        // clear request bit
        BX_DMA_THIS s[ma_sl].status_reg &= ~(1 << (channel+4));
        BX_DEBUG(("DMA-%d: cleared request bit for channel %u", ma_sl+1, channel));
      }
      control_HRQ(ma_sl);
      break;

    case 0x0a:
    case 0xd4:
      set_mask_bit = value & 0x04;
      channel = value & 0x03;
      BX_DMA_THIS s[ma_sl].mask[channel] = (set_mask_bit > 0);
      BX_DEBUG(("DMA-%d: set_mask_bit=%u, channel=%u, mask now=%02xh", ma_sl+1,
          set_mask_bit, channel, BX_DMA_THIS s[ma_sl].mask[channel]));
      control_HRQ(ma_sl);
      break;

    case 0x0b: /* DMA-1 mode register */
    case 0xd6: /* DMA-2 mode register */
      channel = value & 0x03;
      BX_DMA_THIS s[ma_sl].chan[channel].mode.mode_type = (value >> 6) & 0x03;
      BX_DMA_THIS s[ma_sl].chan[channel].mode.address_decrement = (value >> 5) & 0x01;
      BX_DMA_THIS s[ma_sl].chan[channel].mode.autoinit_enable = (value >> 4) & 0x01;
      BX_DMA_THIS s[ma_sl].chan[channel].mode.transfer_type = (value >> 2) & 0x03;
      BX_DEBUG(("DMA-%d: mode register[%u] = %02x", ma_sl+1, channel, (unsigned) value));
      break;

    case 0x0c: /* DMA-1 clear byte flip/flop */
    case 0xd8: /* DMA-2 clear byte flip/flop */
      BX_DEBUG(("DMA-%d: clear flip/flop", ma_sl+1));
      BX_DMA_THIS s[ma_sl].flip_flop = 0;
      break;

    case 0x0d: // DMA-1: master clear
    case 0xda: // DMA-2: master clear
      BX_DEBUG(("DMA-%d: master clear", ma_sl+1));
      // writing any value to this port resets DMA controller 1 / 2
      // same action as a hardware reset
      // mask register is set (chan 0..3 disabled)
      // command, status, request, temporary, and byte flip-flop are all cleared
      reset_controller(ma_sl);
      break;

    case 0x0e: // DMA-1: clear mask register
    case 0xdc: // DMA-2: clear mask register
      BX_DEBUG(("DMA-%d: clear mask register", ma_sl+1));
      BX_DMA_THIS s[ma_sl].mask[0] = 0;
      BX_DMA_THIS s[ma_sl].mask[1] = 0;
      BX_DMA_THIS s[ma_sl].mask[2] = 0;
      BX_DMA_THIS s[ma_sl].mask[3] = 0;
      control_HRQ(ma_sl);
      break;

    case 0x0f: // DMA-1: write all mask bits
    case 0xde: // DMA-2: write all mask bits
      BX_DEBUG(("DMA-%d: write all mask bits", ma_sl+1));
      BX_DMA_THIS s[ma_sl].mask[0] = value & 0x01; value >>= 1;
      BX_DMA_THIS s[ma_sl].mask[1] = value & 0x01; value >>= 1;
      BX_DMA_THIS s[ma_sl].mask[2] = value & 0x01; value >>= 1;
      BX_DMA_THIS s[ma_sl].mask[3] = value & 0x01;
      control_HRQ(ma_sl);
      break;

    case 0x81: /* DMA-1 page register, channel 2 */
    case 0x82: /* DMA-1 page register, channel 3 */
    case 0x83: /* DMA-1 page register, channel 1 */
    case 0x87: /* DMA-1 page register, channel 0 */
      /* address bits A16-A23 for DMA channel */
      channel = channelindex[address - 0x81];
      BX_DMA_THIS s[0].chan[channel].page_reg = value;
      BX_DEBUG(("DMA-1: page register %d = %02x", channel, (unsigned) value));
      break;

    case 0x89: /* DMA-2 page register, channel 2 */
    case 0x8a: /* DMA-2 page register, channel 3 */
    case 0x8b: /* DMA-2 page register, channel 1 */
    case 0x8f: /* DMA-2 page register, channel 0 */
      /* address bits A16-A23 for DMA channel */
      channel = channelindex[address - 0x89];
      BX_DMA_THIS s[1].chan[channel].page_reg = value;
      BX_DEBUG(("DMA-2: page register %d = %02x", channel + 4, (unsigned) value));
      break;

    case 0x0080:
    case 0x0084:
    case 0x0085:
    case 0x0086:
    case 0x0088:
    case 0x008c:
    case 0x008d:
    case 0x008e:
      BX_DEBUG(("write: extra page register 0x%04x (unused)", (unsigned) address));
      BX_DMA_THIS ext_page_reg[address & 0x0f] = value;
      break;

    default:
      BX_ERROR(("write ignored: %04xh = %02xh",
        (unsigned) address, (unsigned) value));
  }
}

void bx_dma_c::set_DRQ(unsigned channel, bool val)
{
  Bit32u dma_base, dma_roof;
  Bit8u ma_sl;

  if (channel > 7) {
    BX_PANIC(("set_DRQ() channel > 7"));
    return;
  }
  ma_sl = (channel > 3) ? 1 : 0;
  BX_DMA_THIS s[ma_sl].DRQ[channel & 0x03] = val;
  if (!BX_DMA_THIS s[ma_sl].chan[channel & 0x03].used) {
    BX_PANIC(("set_DRQ(): channel %d not connected to device", channel));
    return;
  }
  channel &= 0x03;
  if (!val) {
    // clear bit in status reg
    BX_DMA_THIS s[ma_sl].status_reg &= ~(1 << (channel+4));

    control_HRQ(ma_sl);
    return;
  }

  BX_DMA_THIS s[ma_sl].status_reg |= (1 << (channel+4));

  if ((BX_DMA_THIS s[ma_sl].chan[channel].mode.mode_type != DMA_MODE_SINGLE) &&
      (BX_DMA_THIS s[ma_sl].chan[channel].mode.mode_type != DMA_MODE_DEMAND) &&
      (BX_DMA_THIS s[ma_sl].chan[channel].mode.mode_type != DMA_MODE_CASCADE))
  {
    BX_PANIC(("set_DRQ: mode_type(%02x) not handled",
      BX_DMA_THIS s[ma_sl].chan[channel].mode.mode_type));
  }

  dma_base = (BX_DMA_THIS s[ma_sl].chan[channel].page_reg << 16) |
             (BX_DMA_THIS s[ma_sl].chan[channel].base_address << ma_sl);
  if (BX_DMA_THIS s[ma_sl].chan[channel].mode.address_decrement==0) {
    dma_roof = dma_base + (BX_DMA_THIS s[ma_sl].chan[channel].base_count << ma_sl);
  } else {
    dma_roof = dma_base - (BX_DMA_THIS s[ma_sl].chan[channel].base_count << ma_sl);
  }
  if ((dma_base & (0x7fff0000 << ma_sl)) != (dma_roof & (0x7fff0000 << ma_sl))) {
    BX_INFO(("dma_base = 0x%08x", dma_base));
    BX_INFO(("dma_base_count = 0x%08x", BX_DMA_THIS s[ma_sl].chan[channel].base_count));
    BX_INFO(("dma_roof = 0x%08x", dma_roof));
    BX_PANIC(("request outside %dk boundary", 64 << ma_sl));
  }

  control_HRQ(ma_sl);
}

void bx_dma_c::control_HRQ(Bit8u ma_sl)
{
  unsigned channel;

  // do nothing if controller is disabled
  if (BX_DMA_THIS s[ma_sl].ctrl_disabled)
    return;

  // deassert HRQ if no DRQ is pending
  if ((BX_DMA_THIS s[ma_sl].status_reg & 0xf0) == 0) {
    if (ma_sl) {
      bx_pc_system.set_HRQ(0);
    } else {
      BX_DMA_THIS set_DRQ(4, 0);
    }
    return;
  }
  // find highest priority channel
  for (channel=0; channel<4; channel++) {
    if ((BX_DMA_THIS s[ma_sl].status_reg & (1 << (channel+4))) &&
        (BX_DMA_THIS s[ma_sl].mask[channel]==0)) {
      if (ma_sl) {
        // assert Hold ReQuest line to CPU
        bx_pc_system.set_HRQ(1);
      } else {
        // send DRQ to cascade channel of the master
        BX_DMA_THIS set_DRQ(4, 1);
      }
      break;
    }
  }
}

void bx_dma_c::raise_HLDA(void)
{
  unsigned channel;
  bx_phy_address phy_addr;
  Bit8u ma_sl = 0;
  Bit16u maxlen, len = 1;
  Bit8u buffer[BX_DMA_BUFFER_SIZE];

  BX_DMA_THIS HLDA = 1;
  // find highest priority channel
  for (channel=0; channel<4; channel++) {
    if ((BX_DMA_THIS s[1].status_reg & (1 << (channel+4))) &&
        (BX_DMA_THIS s[1].mask[channel]==0)) {
      ma_sl = 1;
      break;
    }
  }
  if (channel == 0) { // master cascade channel
    BX_DMA_THIS s[1].DACK[0] = 1;
    for (channel=0; channel<4; channel++) {
      if ((BX_DMA_THIS s[0].status_reg & (1 << (channel+4))) &&
          (BX_DMA_THIS s[0].mask[channel]==0)) {
        ma_sl = 0;
        break;
      }
    }
  }
  if (channel >= 4) {
    // wait till they're unmasked
    return;
  }

  phy_addr = (BX_DMA_THIS s[ma_sl].chan[channel].page_reg << 16) |
             (BX_DMA_THIS s[ma_sl].chan[channel].current_address << ma_sl);

  if (!BX_DMA_THIS s[ma_sl].chan[channel].mode.address_decrement) {
    maxlen = (BX_DMA_THIS s[ma_sl].chan[channel].current_count + 1) << ma_sl;
    BX_DMA_THIS TC = (maxlen <= BX_DMA_BUFFER_SIZE);
    if (maxlen > BX_DMA_BUFFER_SIZE) {
      maxlen = BX_DMA_BUFFER_SIZE;
    }
  } else {
    BX_DMA_THIS TC = (BX_DMA_THIS s[ma_sl].chan[channel].current_count == 0);
    maxlen = 1 << ma_sl;
  }

  if (BX_DMA_THIS s[ma_sl].chan[channel].mode.transfer_type == 1) { // write
    // DMA controlled xfer of bytes from I/O to Memory

    if (!ma_sl) {
      if (BX_DMA_THIS h[channel].dmaWrite8)
        len = BX_DMA_THIS h[channel].dmaWrite8(buffer, maxlen);
      else
        BX_PANIC(("no dmaWrite handler for channel %u.", channel));

      DEV_MEM_WRITE_PHYSICAL_DMA(phy_addr, len, buffer);

      BX_DBG_DMA_REPORT(phy_addr, len, BX_WRITE, buffer[0]); // FIXME
    } else {
      if (BX_DMA_THIS h[channel].dmaWrite16)
        len = BX_DMA_THIS h[channel].dmaWrite16((Bit16u*)buffer, maxlen / 2);
      else
        BX_PANIC(("no dmaWrite handler for channel %u.", channel));

      DEV_MEM_WRITE_PHYSICAL_DMA(phy_addr, len, buffer);

      BX_DBG_DMA_REPORT(phy_addr, len * 2, BX_WRITE, buffer[0] | (buffer[1] << 16)); // FIXME
    }
  } else if (BX_DMA_THIS s[ma_sl].chan[channel].mode.transfer_type == 2) { // read
    // DMA controlled xfer of bytes from Memory to I/O

    if (!ma_sl) {
      DEV_MEM_READ_PHYSICAL_DMA(phy_addr, maxlen, buffer);

      if (BX_DMA_THIS h[channel].dmaRead8)
        len = BX_DMA_THIS h[channel].dmaRead8(buffer, maxlen);

      BX_DBG_DMA_REPORT(phy_addr, len, BX_READ, buffer[0]); // FIXME
    } else {
      DEV_MEM_READ_PHYSICAL_DMA(phy_addr, maxlen, buffer);

      if (BX_DMA_THIS h[channel].dmaRead16)
        len = BX_DMA_THIS h[channel].dmaRead16((Bit16u*)buffer, maxlen / 2);

      BX_DBG_DMA_REPORT(phy_addr, len * 2, BX_READ, buffer[0] | (buffer[1] << 16)); // FIXME
    }
  } else if (BX_DMA_THIS s[ma_sl].chan[channel].mode.transfer_type == 0) {
    // verify

    if (!ma_sl) {
      if (BX_DMA_THIS h[channel].dmaWrite8)
        len = BX_DMA_THIS h[channel].dmaWrite8(buffer, 1);
      else
        BX_PANIC(("no dmaWrite handler for channel %u.", channel));
    } else {
      if (BX_DMA_THIS h[channel].dmaWrite16)
        len = BX_DMA_THIS h[channel].dmaWrite16((Bit16u*)buffer, 1);
      else
        BX_PANIC(("no dmaWrite handler for channel %u.", channel));
    }
  } else {
    BX_PANIC(("hlda: transfer_type 3 is undefined"));
  }

  BX_DMA_THIS s[ma_sl].DACK[channel] = 1;
  // check for expiration of count, so we can signal TC and DACK(n)
  // at the same time.
  if (!BX_DMA_THIS s[ma_sl].chan[channel].mode.address_decrement)
    BX_DMA_THIS s[ma_sl].chan[channel].current_address += len;
  else
    BX_DMA_THIS s[ma_sl].chan[channel].current_address--;
  BX_DMA_THIS s[ma_sl].chan[channel].current_count -= len;
  if (BX_DMA_THIS s[ma_sl].chan[channel].current_count == 0xffff) {
    // count expired, done with transfer
    // assert TC, deassert HRQ & DACK(n) lines
    BX_DMA_THIS s[ma_sl].status_reg |= (1 << channel); // hold TC in status reg
    if (BX_DMA_THIS s[ma_sl].chan[channel].mode.autoinit_enable == 0) {
      // set mask bit if not in autoinit mode
      BX_DMA_THIS s[ma_sl].mask[channel] = 1;
    } else {
      // count expired, but in autoinit mode
      // reload count and base address
      BX_DMA_THIS s[ma_sl].chan[channel].current_address =
        BX_DMA_THIS s[ma_sl].chan[channel].base_address;
      BX_DMA_THIS s[ma_sl].chan[channel].current_count =
        BX_DMA_THIS s[ma_sl].chan[channel].base_count;
    }
    BX_DMA_THIS TC = 0;            // clear TC, adapter card already notified
    BX_DMA_THIS HLDA = 0;
    bx_pc_system.set_HRQ(0);           // clear HRQ to CPU
    BX_DMA_THIS s[ma_sl].DACK[channel] = 0; // clear DACK to adapter card
    if (!ma_sl) {
      BX_DMA_THIS set_DRQ(4, 0); // clear DRQ to cascade
      BX_DMA_THIS s[1].DACK[0] = 0; // clear DACK to cascade
    }
  }
}

#if BX_DEBUGGER
void bx_dma_c::debug_dump(int argc, char **argv)
{
  int ch, i, j;

  dbg_printf("i8237A DMA controller\n\n");
  for (i = 0; i < 2; i++) {
    for (j = 0; j < 4; j++) {
      if (BX_DMA_THIS s[i].chan[j].used) {
        ch = i * 4 + j;
        dbg_printf("DMA channel %d", ch);
        if (ch == 4) {
          dbg_printf(" (cascade)\n");
        } else if (BX_DMA_THIS s[i].DRQ[j] && !BX_DMA_THIS s[i].mask[j]) {
          dbg_printf(" (active)\n");
          dbg_printf("  address: base=0x%04x, current=0x%04x\n", BX_DMA_THIS s[i].chan[j].base_address,
                     BX_DMA_THIS s[i].chan[j].current_address);
          dbg_printf("  count: base=0x%04x, current=0x%04x\n", BX_DMA_THIS s[i].chan[j].base_count,
                     BX_DMA_THIS s[i].chan[j].current_count);
          dbg_printf("  page: 0x%02x\n", BX_DMA_THIS s[i].chan[j].page_reg);
          dbg_printf("  mask: %u\n", BX_DMA_THIS s[i].mask[j]);
          dbg_printf("  flip_flop: %u\n", BX_DMA_THIS s[i].flip_flop);
          dbg_printf("  status_reg: 0x%02x\n", BX_DMA_THIS s[i].status_reg);
          dbg_printf("  mode_type: %u\n", BX_DMA_THIS s[i].chan[j].mode.mode_type);
          dbg_printf("  address_decrement: %u\n", BX_DMA_THIS s[i].chan[j].mode.address_decrement);
          dbg_printf("  autoinit_enable: %u\n", BX_DMA_THIS s[i].chan[j].mode.autoinit_enable);
          dbg_printf("  transfer_type: %u\n", BX_DMA_THIS s[i].chan[j].mode.transfer_type);
        } else {
          dbg_printf(" (not active: DRQ=%u, mask=%u)\n", BX_DMA_THIS s[i].DRQ[j],
                     BX_DMA_THIS s[i].mask[j]);
        }
      }
    }
  }
  if (argc > 0) {
    dbg_printf("\nAdditional options not supported\n");
  }
}
#endif
