/////////////////////////////////////////////////////////////////////////
// $Id: ne2k.cc 13497 2018-05-01 15:54:37Z vruppert $
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2001-2018  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
/////////////////////////////////////////////////////////////////////////

// Peter Grehan (grehan@iprg.nokia.com) coded the initial version of this
// NE2000/ether stuff.

// 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"

#if BX_SUPPORT_NE2K

#if BX_SUPPORT_PCI
#include "pci.h"
#endif
#include "ne2k.h"
#include "netmod.h"

//Never completely fill the ne2k ring so that we never
// hit the unclear completely full buffer condition.
#define BX_NE2K_NEVER_FULL_RING (1)

#define LOG_THIS theNE2kDevice->

bx_ne2k_c *theNE2kDevice = NULL;

const Bit8u ne2k_iomask[32] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
                               7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};

// builtin configuration handling functions

void ne2k_init_options(void)
{
  bx_param_c *network = SIM->get_param("network");
  bx_list_c *menu = new bx_list_c(network, "ne2k", "NE2000");
  menu->set_options(menu->SHOW_PARENT);
  bx_param_bool_c *enabled = new bx_param_bool_c(menu,
    "enabled",
    "Enable NE2K NIC emulation",
    "Enables the NE2K NIC emulation",
    1);
  bx_param_num_c *ioaddr = new bx_param_num_c(menu,
    "ioaddr",
    "NE2K I/O Address",
    "I/O base address of the emulated NE2K device",
    0, 0xffff,
    0x300);
  ioaddr->set_base(16);
  bx_param_num_c *irq = new bx_param_num_c(menu,
    "irq",
    "NE2K Interrupt",
    "IRQ used by the NE2K device",
    0, 15,
    9);
  irq->set_options(irq->USE_SPIN_CONTROL);
  SIM->init_std_nic_options("NE2K", menu);
  enabled->set_dependent_list(menu->clone());
}

Bit32s ne2k_options_parser(const char *context, int num_params, char *params[])
{
  int ret, valid = 0;

  if (!strcmp(params[0], "ne2k")) {
    bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_NE2K);
    if (!SIM->get_param_bool("enabled", base)->get()) {
      SIM->get_param_enum("ethmod", base)->set_by_name("null");
    }
    if (SIM->is_pci_device(BX_PLUGIN_NE2K)) {
      valid |= 0x03;
    }
    if (!SIM->get_param_string("mac", base)->isempty()) {
      // MAC address is already initialized
      valid |= 0x04;
    }
    for (int i = 1; i < num_params; i++) {
      if (!strncmp(params[i], "ioaddr=", 7)) {
        SIM->get_param_num("ioaddr", base)->set(strtoul(&params[i][7], NULL, 16));
        valid |= 0x01;
      } else if (!strncmp(params[i], "irq=", 4)) {
        SIM->get_param_num("irq", base)->set(atol(&params[i][4]));
        valid |= 0x02;
      } else {
        if (valid == 0x07) {
          SIM->get_param_bool("enabled", base)->set(1);
        }
        ret = SIM->parse_nic_params(context, params[i], base);
        if (ret > 0) {
          valid |= ret;
        }
      }
    }
    if (valid < 0x80) {
      if ((valid & 0x03) != 0x03) {
        BX_ERROR(("%s: 'ne2k' directive incomplete (ioaddr and irq are required)", context));
      }
      if ((valid & 0x04) == 0) {
        BX_ERROR(("%s: 'ne2k' directive incomplete (mac address is required)", context));
      }
    }
  } else {
    BX_PANIC(("%s: unknown directive '%s'", context, params[0]));
  }
  return 0;
}

Bit32s ne2k_options_save(FILE *fp)
{
  return SIM->write_param_list(fp, (bx_list_c*) SIM->get_param(BXPN_NE2K), NULL, 0);
}

// device plugin entry points

int CDECL libne2k_LTX_plugin_init(plugin_t *plugin, plugintype_t type)
{
  theNE2kDevice = new bx_ne2k_c();
  BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theNE2kDevice, BX_PLUGIN_NE2K);
  // add new configuration parameter for the config interface
  ne2k_init_options();
  // register add-on option for bochsrc and command line
  SIM->register_addon_option("ne2k", ne2k_options_parser, ne2k_options_save);
  return(0); // Success
}

void CDECL libne2k_LTX_plugin_fini(void)
{
  SIM->unregister_addon_option("ne2k");
  ((bx_list_c*)SIM->get_param("network"))->remove("ne2k");
  delete theNE2kDevice;
}

// the device object

bx_ne2k_c::bx_ne2k_c()
{
  put("NE2K");
  memset(&s, 0, sizeof(bx_ne2k_t));
  s.tx_timer_index = BX_NULL_TIMER_HANDLE;
  ethdev = NULL;
}


bx_ne2k_c::~bx_ne2k_c()
{
  if (ethdev != NULL) {
    delete ethdev;
  }
  SIM->get_bochs_root()->remove("ne2k");
  BX_DEBUG(("Exit"));
}

void bx_ne2k_c::init(void)
{
  static char devname[16];
  Bit8u macaddr[6];
  bx_param_string_c *bootrom;

  BX_DEBUG(("Init $Id: ne2k.cc 13497 2018-05-01 15:54:37Z vruppert $"));

  // Read in values from config interface
  bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_NE2K);
  // Check if the device is disabled or not configured
  if (!SIM->get_param_bool("enabled", base)->get()) {
    BX_INFO(("NE2000 disabled"));
    // mark unused plugin for removal
    ((bx_param_bool_c*)((bx_list_c*)SIM->get_param(BXPN_PLUGIN_CTRL))->get_by_name("ne2k"))->set(0);
    return;
  }
  memcpy(macaddr, SIM->get_param_string("mac", base)->getptr(), 6);
  strcpy(devname, "NE2000 NIC");
  BX_NE2K_THIS s.pci_enabled = SIM->is_pci_device(BX_PLUGIN_NE2K);

#if BX_SUPPORT_PCI
  if (BX_NE2K_THIS s.pci_enabled) {
    strcpy(devname, "NE2000 PCI NIC");
    BX_NE2K_THIS s.devfunc = 0x00;
    DEV_register_pci_handlers(this, &BX_NE2K_THIS s.devfunc,
        BX_PLUGIN_NE2K, devname);

    // initialize readonly registers
    init_pci_conf(0x10ec, 0x8029, 0x00, 0x020000, 0x00, BX_PCI_INTA);
    BX_NE2K_THIS pci_conf[0x04] = 0x01;
    BX_NE2K_THIS pci_conf[0x07] = 0x02;
    init_bar_io(0, 32, read_handler, write_handler, &ne2k_iomask[0]);
    BX_NE2K_THIS s.base_address = 0x0;
    BX_NE2K_THIS pci_rom_address = 0;
    BX_NE2K_THIS pci_rom_read_handler = mem_read_handler;
    bootrom = SIM->get_param_string("bootrom", base);
    if (!bootrom->isempty()) {
      BX_NE2K_THIS load_pci_rom(bootrom->getptr());
    }
  }
#endif

  if (BX_NE2K_THIS s.tx_timer_index == BX_NULL_TIMER_HANDLE) {
    BX_NE2K_THIS s.tx_timer_index =
      DEV_register_timer(this, tx_timer_handler, 0, 0, 0,
                         "ne2k"); // one-shot, inactive
  }
  // Register the IRQ and i/o port addresses
  if (!BX_NE2K_THIS s.pci_enabled) {
    BX_NE2K_THIS s.base_address = SIM->get_param_num("ioaddr", base)->get();
    BX_NE2K_THIS s.base_irq     = SIM->get_param_num("irq", base)->get();

    DEV_register_irq(BX_NE2K_THIS s.base_irq, "NE2000 ethernet NIC");

    DEV_register_ioread_handler_range(BX_NE2K_THIS_PTR, read_handler,
                                      BX_NE2K_THIS s.base_address,
                                      BX_NE2K_THIS s.base_address + 0x0F,
                                      devname, 3);
    DEV_register_iowrite_handler_range(BX_NE2K_THIS_PTR, write_handler,
                                       BX_NE2K_THIS s.base_address,
                                       BX_NE2K_THIS s.base_address + 0x0F,
                                       devname, 3);
    DEV_register_ioread_handler(BX_NE2K_THIS_PTR, read_handler,
                                BX_NE2K_THIS s.base_address + 0x10,
                                devname, 3);
    DEV_register_iowrite_handler(BX_NE2K_THIS_PTR, write_handler,
                                 BX_NE2K_THIS s.base_address + 0x10,
                                 devname, 3);
    DEV_register_ioread_handler(BX_NE2K_THIS_PTR, read_handler,
                                BX_NE2K_THIS s.base_address + 0x1F,
                                devname, 1);
    DEV_register_iowrite_handler(BX_NE2K_THIS_PTR, write_handler,
                                 BX_NE2K_THIS s.base_address + 0x1F,
                                 devname, 1);

    bootrom = SIM->get_param_string("bootrom", base);
    if (!bootrom->isempty()) {
      BX_PANIC(("%s: boot ROM support not present yet", devname));
    }

    BX_INFO(("%s initialized port 0x%x/32 irq %d mac %02x:%02x:%02x:%02x:%02x:%02x",
             devname,
             BX_NE2K_THIS s.base_address,
             BX_NE2K_THIS s.base_irq,
             macaddr[0], macaddr[1],
             macaddr[2], macaddr[3],
             macaddr[4], macaddr[5]));
  } else {
    BX_INFO(("%s initialized mac %02x:%02x:%02x:%02x:%02x:%02x",
             devname,
             macaddr[0], macaddr[1],
             macaddr[2], macaddr[3],
             macaddr[4], macaddr[5]));
  }

  // Initialise the mac address area by doubling the physical address
  BX_NE2K_THIS s.macaddr[0]  = macaddr[0];
  BX_NE2K_THIS s.macaddr[1]  = macaddr[0];
  BX_NE2K_THIS s.macaddr[2]  = macaddr[1];
  BX_NE2K_THIS s.macaddr[3]  = macaddr[1];
  BX_NE2K_THIS s.macaddr[4]  = macaddr[2];
  BX_NE2K_THIS s.macaddr[5]  = macaddr[2];
  BX_NE2K_THIS s.macaddr[6]  = macaddr[3];
  BX_NE2K_THIS s.macaddr[7]  = macaddr[3];
  BX_NE2K_THIS s.macaddr[8]  = macaddr[4];
  BX_NE2K_THIS s.macaddr[9]  = macaddr[4];
  BX_NE2K_THIS s.macaddr[10] = macaddr[5];
  BX_NE2K_THIS s.macaddr[11] = macaddr[5];

  // ne2k signature
  for (int i = 12; i < 32; i++)
    BX_NE2K_THIS s.macaddr[i] = 0x57;

  BX_NE2K_THIS s.statusbar_id = bx_gui->register_statusitem("NE2K", 1);

  // Attach to the selected ethernet module
  BX_NE2K_THIS ethdev = DEV_net_init_module(base, rx_handler, rx_status_handler, this);

#if BX_DEBUGGER
  // register device for the 'info device' command (calls debug_dump())
  bx_dbg_register_debug_info("ne2k", this);
#endif
}

//
// reset - restore state to power-up, cancelling all i/o
//
void bx_ne2k_c::reset(unsigned type)
{
  if (type == BX_RESET_HARDWARE) {
    // Zero out registers and memory
    memset(&BX_NE2K_THIS s.CR,  0, sizeof(BX_NE2K_THIS s.CR));
    memset(&BX_NE2K_THIS s.IMR, 0, sizeof(BX_NE2K_THIS s.IMR));
    memset(&BX_NE2K_THIS s.DCR, 0, sizeof(BX_NE2K_THIS s.DCR));
    memset(&BX_NE2K_THIS s.TCR, 0, sizeof(BX_NE2K_THIS s.TCR));
    memset(&BX_NE2K_THIS s.TSR, 0, sizeof(BX_NE2K_THIS s.TSR));
    memset(&BX_NE2K_THIS s.RCR, 0, sizeof(BX_NE2K_THIS s.RCR));
    memset(&BX_NE2K_THIS s.RSR, 0, sizeof(BX_NE2K_THIS s.RSR));
    BX_NE2K_THIS s.local_dma  = 0;
    BX_NE2K_THIS s.page_start = 0;
    BX_NE2K_THIS s.page_stop  = 0;
    BX_NE2K_THIS s.bound_ptr  = 0;
    BX_NE2K_THIS s.tx_page_start = 0;
    BX_NE2K_THIS s.num_coll   = 0;
    BX_NE2K_THIS s.tx_bytes   = 0;
    BX_NE2K_THIS s.fifo       = 0;
    BX_NE2K_THIS s.remote_dma = 0;
    BX_NE2K_THIS s.remote_start = 0;
    BX_NE2K_THIS s.remote_bytes = 0;
    BX_NE2K_THIS s.tallycnt_0 = 0;
    BX_NE2K_THIS s.tallycnt_1 = 0;
    BX_NE2K_THIS s.tallycnt_2 = 0;

    memset(&BX_NE2K_THIS s.physaddr, 0, sizeof(BX_NE2K_THIS s.physaddr));
    memset(&BX_NE2K_THIS s.mchash, 0, sizeof(BX_NE2K_THIS s.mchash));
    BX_NE2K_THIS s.curr_page = 0;

    BX_NE2K_THIS s.rempkt_ptr   = 0;
    BX_NE2K_THIS s.localpkt_ptr = 0;
    BX_NE2K_THIS s.address_cnt  = 0;

    memset(&BX_NE2K_THIS s.mem, 0, sizeof(BX_NE2K_THIS s.mem));

    // Set power-up conditions
    BX_NE2K_THIS s.CR.stop      = 1;
    BX_NE2K_THIS s.CR.rdma_cmd  = 4;
    BX_NE2K_THIS s.DCR.longaddr = 1;

    set_irq_level(0);
  }
  memset(&BX_NE2K_THIS s.ISR, 0, sizeof(BX_NE2K_THIS s.ISR));
  BX_NE2K_THIS s.ISR.reset = 1;
}

void bx_ne2k_c::register_state(void)
{
  bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "ne2k", "NE2000 State");
  bx_list_c *CR = new bx_list_c(list, "CR");
  new bx_shadow_bool_c(CR, "stop", &BX_NE2K_THIS s.CR.stop);
  new bx_shadow_bool_c(CR, "start", &BX_NE2K_THIS s.CR.start);
  new bx_shadow_bool_c(CR, "tx_packet", &BX_NE2K_THIS s.CR.tx_packet);
  new bx_shadow_num_c(CR, "rdma_cmd", &BX_NE2K_THIS s.CR.rdma_cmd);
  new bx_shadow_num_c(CR, "pgsel", &BX_NE2K_THIS s.CR.pgsel);
  bx_list_c *ISR = new bx_list_c(list, "ISR");
  new bx_shadow_bool_c(ISR, "pkt_rx", &BX_NE2K_THIS s.ISR.pkt_rx);
  new bx_shadow_bool_c(ISR, "pkt_tx", &BX_NE2K_THIS s.ISR.pkt_tx);
  new bx_shadow_bool_c(ISR, "rx_err", &BX_NE2K_THIS s.ISR.rx_err);
  new bx_shadow_bool_c(ISR, "tx_err", &BX_NE2K_THIS s.ISR.tx_err);
  new bx_shadow_bool_c(ISR, "overwrite", &BX_NE2K_THIS s.ISR.overwrite);
  new bx_shadow_bool_c(ISR, "cnt_oflow", &BX_NE2K_THIS s.ISR.cnt_oflow);
  new bx_shadow_bool_c(ISR, "rdma_done", &BX_NE2K_THIS s.ISR.rdma_done);
  new bx_shadow_bool_c(ISR, "reset", &BX_NE2K_THIS s.ISR.reset);
  bx_list_c *IMR = new bx_list_c(list, "IMR");
  new bx_shadow_bool_c(IMR, "rx_inte", &BX_NE2K_THIS s.IMR.rx_inte);
  new bx_shadow_bool_c(IMR, "tx_inte", &BX_NE2K_THIS s.IMR.tx_inte);
  new bx_shadow_bool_c(IMR, "rxerr_inte", &BX_NE2K_THIS s.IMR.rxerr_inte);
  new bx_shadow_bool_c(IMR, "txerr_inte", &BX_NE2K_THIS s.IMR.txerr_inte);
  new bx_shadow_bool_c(IMR, "overw_inte", &BX_NE2K_THIS s.IMR.overw_inte);
  new bx_shadow_bool_c(IMR, "cofl_inte", &BX_NE2K_THIS s.IMR.cofl_inte);
  new bx_shadow_bool_c(IMR, "rdma_inte", &BX_NE2K_THIS s.IMR.rdma_inte);
  bx_list_c *DCR = new bx_list_c(list, "DCR");
  new bx_shadow_bool_c(DCR, "wdsize", &BX_NE2K_THIS s.DCR.wdsize);
  new bx_shadow_bool_c(DCR, "endian", &BX_NE2K_THIS s.DCR.endian);
  new bx_shadow_bool_c(DCR, "longaddr", &BX_NE2K_THIS s.DCR.longaddr);
  new bx_shadow_bool_c(DCR, "loop", &BX_NE2K_THIS s.DCR.loop);
  new bx_shadow_bool_c(DCR, "auto_rx", &BX_NE2K_THIS s.DCR.auto_rx);
  new bx_shadow_num_c(DCR, "fifo_size", &BX_NE2K_THIS s.DCR.fifo_size);
  bx_list_c *TCR = new bx_list_c(list, "TCR");
  new bx_shadow_bool_c(TCR, "crc_disable", &BX_NE2K_THIS s.TCR.crc_disable);
  new bx_shadow_num_c(TCR, "loop_cntl", &BX_NE2K_THIS s.TCR.loop_cntl);
  new bx_shadow_bool_c(TCR, "ext_stoptx", &BX_NE2K_THIS s.TCR.ext_stoptx);
  new bx_shadow_bool_c(TCR, "coll_prio", &BX_NE2K_THIS s.TCR.coll_prio);
  bx_list_c *TSR = new bx_list_c(list, "TSR");
  new bx_shadow_bool_c(TSR, "tx_ok", &BX_NE2K_THIS s.TSR.tx_ok);
  new bx_shadow_bool_c(TSR, "collided", &BX_NE2K_THIS s.TSR.collided);
  new bx_shadow_bool_c(TSR, "aborted", &BX_NE2K_THIS s.TSR.aborted);
  new bx_shadow_bool_c(TSR, "no_carrier", &BX_NE2K_THIS s.TSR.no_carrier);
  new bx_shadow_bool_c(TSR, "fifo_ur", &BX_NE2K_THIS s.TSR.fifo_ur);
  new bx_shadow_bool_c(TSR, "cd_hbeat", &BX_NE2K_THIS s.TSR.cd_hbeat);
  new bx_shadow_bool_c(TSR, "ow_coll", &BX_NE2K_THIS s.TSR.ow_coll);
  bx_list_c *RCR = new bx_list_c(list, "RCR");
  new bx_shadow_bool_c(RCR, "errors_ok", &BX_NE2K_THIS s.RCR.errors_ok);
  new bx_shadow_bool_c(RCR, "runts_ok", &BX_NE2K_THIS s.RCR.runts_ok);
  new bx_shadow_bool_c(RCR, "broadcast", &BX_NE2K_THIS s.RCR.broadcast);
  new bx_shadow_bool_c(RCR, "multicast", &BX_NE2K_THIS s.RCR.multicast);
  new bx_shadow_bool_c(RCR, "promisc", &BX_NE2K_THIS s.RCR.promisc);
  new bx_shadow_bool_c(RCR, "monitor", &BX_NE2K_THIS s.RCR.monitor);
  bx_list_c *RSR = new bx_list_c(list, "RSR");
  new bx_shadow_bool_c(RSR, "rx_ok", &BX_NE2K_THIS s.RSR.rx_ok);
  new bx_shadow_bool_c(RSR, "bad_crc", &BX_NE2K_THIS s.RSR.bad_crc);
  new bx_shadow_bool_c(RSR, "bad_falign", &BX_NE2K_THIS s.RSR.bad_falign);
  new bx_shadow_bool_c(RSR, "fifo_or", &BX_NE2K_THIS s.RSR.fifo_or);
  new bx_shadow_bool_c(RSR, "rx_missed", &BX_NE2K_THIS s.RSR.rx_missed);
  new bx_shadow_bool_c(RSR, "rx_mbit", &BX_NE2K_THIS s.RSR.rx_mbit);
  new bx_shadow_bool_c(RSR, "rx_disabled", &BX_NE2K_THIS s.RSR.rx_disabled);
  new bx_shadow_bool_c(RSR, "deferred", &BX_NE2K_THIS s.RSR.deferred);
  new bx_shadow_num_c(list, "local_dma", &BX_NE2K_THIS s.local_dma, BASE_HEX);
  new bx_shadow_num_c(list, "page_start", &BX_NE2K_THIS s.page_start, BASE_HEX);
  new bx_shadow_num_c(list, "page_stop", &BX_NE2K_THIS s.page_stop, BASE_HEX);
  new bx_shadow_num_c(list, "bound_ptr", &BX_NE2K_THIS s.bound_ptr, BASE_HEX);
  new bx_shadow_num_c(list, "tx_page_start", &BX_NE2K_THIS s.tx_page_start, BASE_HEX);
  new bx_shadow_num_c(list, "num_coll", &BX_NE2K_THIS s.num_coll, BASE_HEX);
  new bx_shadow_num_c(list, "tx_bytes", &BX_NE2K_THIS s.tx_bytes, BASE_HEX);
  new bx_shadow_num_c(list, "fifo", &BX_NE2K_THIS s.fifo, BASE_HEX);
  new bx_shadow_num_c(list, "remote_dma", &BX_NE2K_THIS s.remote_dma, BASE_HEX);
  new bx_shadow_num_c(list, "remote_start", &BX_NE2K_THIS s.remote_start, BASE_HEX);
  new bx_shadow_num_c(list, "remote_bytes", &BX_NE2K_THIS s.remote_bytes, BASE_HEX);
  new bx_shadow_num_c(list, "tallycnt_0", &BX_NE2K_THIS s.tallycnt_0, BASE_HEX);
  new bx_shadow_num_c(list, "tallycnt_1", &BX_NE2K_THIS s.tallycnt_1, BASE_HEX);
  new bx_shadow_num_c(list, "tallycnt_2", &BX_NE2K_THIS s.tallycnt_2, BASE_HEX);
  new bx_shadow_data_c(list, "physaddr", BX_NE2K_THIS s.physaddr, 6, 1);
  new bx_shadow_num_c(list, "curr_page", &BX_NE2K_THIS s.curr_page, BASE_HEX);
  new bx_shadow_data_c(list, "mchash", BX_NE2K_THIS s.mchash, 8, 1);
  new bx_shadow_num_c(list, "rempkt_ptr", &BX_NE2K_THIS s.rempkt_ptr, BASE_HEX);
  new bx_shadow_num_c(list, "localpkt_ptr", &BX_NE2K_THIS s.localpkt_ptr, BASE_HEX);
  new bx_shadow_num_c(list, "address_cnt", &BX_NE2K_THIS s.address_cnt, BASE_HEX);
  new bx_shadow_data_c(list, "mem", BX_NE2K_THIS s.mem, BX_NE2K_MEMSIZ);
  new bx_shadow_bool_c(list, "tx_timer_active", &BX_NE2K_THIS s.tx_timer_active);
#if BX_SUPPORT_PCI
  if (BX_NE2K_THIS s.pci_enabled) {
    register_pci_state(list);
  }
#endif
}

#if BX_SUPPORT_PCI
void bx_ne2k_c::after_restore_state(void)
{
  if (BX_NE2K_THIS s.pci_enabled) {
    bx_pci_device_c::after_restore_pci_state(mem_read_handler);
  }
}
#endif

//
// read_cr/write_cr - utility routines for handling reads/writes to
// the Command Register
//
Bit32u bx_ne2k_c::read_cr(void)
{
  Bit32u val =
         (((BX_NE2K_THIS s.CR.pgsel    & 0x03) << 6) |
	  ((BX_NE2K_THIS s.CR.rdma_cmd & 0x07) << 3) |
	  (BX_NE2K_THIS s.CR.tx_packet << 2) |
	  (BX_NE2K_THIS s.CR.start     << 1) |
	  (BX_NE2K_THIS s.CR.stop));
  BX_DEBUG(("read CR returns 0x%02x", val));
  return val;
}

void bx_ne2k_c::write_cr(Bit32u value)
{
  BX_DEBUG(("wrote 0x%02x to CR", value));

  // Validate remote-DMA
  if ((value & 0x38) == 0x00) {
    BX_DEBUG(("CR write - invalid rDMA value 0"));
    value |= 0x20; /* dma_cmd == 4 is a safe default */
  }

  // Check for s/w reset
  if (value & 0x01) {
    BX_NE2K_THIS s.ISR.reset = 1;
    BX_NE2K_THIS s.CR.stop   = 1;
  } else {
    BX_NE2K_THIS s.CR.stop = 0;
  }

  BX_NE2K_THIS s.CR.rdma_cmd = (value & 0x38) >> 3;

  // If start command issued, the RST bit in the ISR
  // must be cleared
  if ((value & 0x02) && !BX_NE2K_THIS s.CR.start) {
    BX_NE2K_THIS s.ISR.reset = 0;
  }

  BX_NE2K_THIS s.CR.start = ((value & 0x02) == 0x02);
  BX_NE2K_THIS s.CR.pgsel = (value & 0xc0) >> 6;

  // Check for send-packet command
  if (BX_NE2K_THIS s.CR.rdma_cmd == 3) {
    // Set up DMA read from receive ring
    BX_NE2K_THIS s.remote_start = BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.bound_ptr * 256;
    BX_NE2K_THIS s.remote_bytes = (Bit16u) chipmem_read(BX_NE2K_THIS s.bound_ptr * 256 + 2, 2);
    BX_INFO(("Sending buffer #x%x length %d",
      BX_NE2K_THIS s.remote_start,
      BX_NE2K_THIS s.remote_bytes));
  }

  // Check for start-tx
  if ((value & 0x04) && BX_NE2K_THIS s.TCR.loop_cntl) {
    if (BX_NE2K_THIS s.TCR.loop_cntl != 1) {
      BX_INFO(("Loop mode %d not supported.", BX_NE2K_THIS s.TCR.loop_cntl));
    } else {
      rx_frame (& BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 - BX_NE2K_MEMSTART],
                BX_NE2K_THIS s.tx_bytes);
    }
  } else if (value & 0x04) {
    if (BX_NE2K_THIS s.CR.stop || (!BX_NE2K_THIS s.CR.start && !BX_NE2K_THIS s.pci_enabled)) {
      if (BX_NE2K_THIS s.tx_bytes == 0) /* njh@bandsman.co.uk */
        return; /* Solaris9 probe */
      BX_PANIC(("CR write - tx start, dev in reset"));
    }

    if (BX_NE2K_THIS s.tx_bytes == 0)
      BX_PANIC(("CR write - tx start, tx bytes == 0"));

    // Send the packet to the system driver
    BX_NE2K_THIS s.CR.tx_packet = 1;
    Bit16u tx_start_ofs = BX_NE2K_THIS s.tx_page_start*256;
    // The following test and decrement is required for Novell Netware
    // 3.11-3.12, see
    // https://lists.gnu.org/archive/html/qemu-devel/2005-03/msg00313.html
    // for the corresponding change in QEMU.
    if (tx_start_ofs >= BX_NE2K_MEMEND)
      tx_start_ofs -= BX_NE2K_MEMSIZ;
    if (tx_start_ofs + BX_NE2K_THIS s.tx_bytes > BX_NE2K_MEMEND)
      BX_PANIC(("tx start with start offset %d and byte count %d would overrun memory",
                tx_start_ofs, BX_NE2K_THIS s.tx_bytes));
    BX_NE2K_THIS ethdev->sendpkt(& BX_NE2K_THIS s.mem[tx_start_ofs - BX_NE2K_MEMSTART], BX_NE2K_THIS s.tx_bytes);

    // some more debug
    if (BX_NE2K_THIS s.tx_timer_active)
      BX_ERROR(("CR write, tx timer still active"));

    // Schedule a timer to trigger a tx-complete interrupt
    // The number of microseconds is the bit-time / 10.
    // The bit-time is the preamble+sfd (64 bits), the
    // inter-frame gap (96 bits), the CRC (4 bytes), and the
    // the number of bits in the frame (s.tx_bytes * 8).
    //
    bx_pc_system.activate_timer(BX_NE2K_THIS s.tx_timer_index,
				(64 + 96 + 4*8 + BX_NE2K_THIS s.tx_bytes*8)/10,
				0); // not continuous
    BX_NE2K_THIS s.tx_timer_active = 1;
    bx_gui->statusbar_setitem(BX_NE2K_THIS s.statusbar_id, 1, 1);
  }

  // Linux probes for an interrupt by setting up a remote-DMA read
  // of 0 bytes with remote-DMA completion interrupts enabled.
  // Detect this here
  if (BX_NE2K_THIS s.CR.rdma_cmd == 0x01 &&
      BX_NE2K_THIS s.CR.start &&
      BX_NE2K_THIS s.remote_bytes == 0) {
    BX_NE2K_THIS s.ISR.rdma_done = 1;
    if (BX_NE2K_THIS s.IMR.rdma_inte) {
      set_irq_level(1);
    }
  }
}

//
// chipmem_read/chipmem_write - access the 64K private RAM.
// The ne2000 memory is accessed through the data port of
// the asic (offset 0) after setting up a remote-DMA transfer.
// Both byte and word accesses are allowed.
// The first 16 bytes contains the MAC address at even locations,
// and there is 16K of buffer memory starting at 16K
//
Bit32u BX_CPP_AttrRegparmN(2)
bx_ne2k_c::chipmem_read(Bit32u address, unsigned int io_len)
{
  Bit32u retval = 0;

  if ((io_len == 2) && (address & 0x1))
    BX_PANIC(("unaligned chipmem word read"));

  // ROM'd MAC address
  if ((address >=0) && (address <= 31)) {
    retval = BX_NE2K_THIS s.macaddr[address];
    if ((io_len == 2) || (io_len == 4)) {
      retval |= (BX_NE2K_THIS s.macaddr[address + 1] << 8);
    }
    if (io_len == 4) {
      retval |= (BX_NE2K_THIS s.macaddr[address + 2] << 16);
      retval |= (BX_NE2K_THIS s.macaddr[address + 3] << 24);
    }
    return (retval);
  }

  if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) {
    retval = BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART];
    if ((io_len == 2) || (io_len == 4)) {
      retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] << 8);
    }
    if (io_len == 4) {
      retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 2] << 16);
      retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 3] << 24);
    }
    return (retval);
  }

  BX_DEBUG(("out-of-bounds chipmem read, %04X", address));

  return (0xff);
}

void BX_CPP_AttrRegparmN(3)
bx_ne2k_c::chipmem_write(Bit32u address, Bit32u value, unsigned io_len)
{
  if ((io_len == 2) && (address & 0x1))
    BX_PANIC(("unaligned chipmem word write"));

  if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) {
    BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART] = value & 0xff;
    if ((io_len == 2) || (io_len == 4)) {
      BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] = value >> 8;
    }
    if (io_len == 4) {
      BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 2] = value >> 16;
      BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 3] = value >> 24;
    }
  } else
    BX_DEBUG(("out-of-bounds chipmem write, %04X", address));
}

//
// asic_read/asic_write - This is the high 16 bytes of i/o space
// (the lower 16 bytes is for the DS8390). Only two locations
// are used: offset 0, which is used for data transfer, and
// offset 0xf, which is used to reset the device.
// The data transfer port is used to as 'external' DMA to the
// DS8390. The chip has to have the DMA registers set up, and
// after that, insw/outsw instructions can be used to move
// the appropriate number of bytes to/from the device.
//
Bit32u BX_CPP_AttrRegparmN(2)
bx_ne2k_c::asic_read(Bit32u offset, unsigned int io_len)
{
  Bit32u retval = 0;

  switch (offset) {
  case 0x0:  // Data register
    //
    // A read remote-DMA command must have been issued,
    // and the source-address and length registers must
    // have been initialised.
    //
    if (io_len > BX_NE2K_THIS s.remote_bytes) {
      BX_ERROR(("ne2K: dma read underrun iolen=%d remote_bytes=%d",io_len,BX_NE2K_THIS s.remote_bytes));
      //return 0;
    }

    //BX_INFO(("ne2k read DMA: addr=%4x remote_bytes=%d",BX_NE2K_THIS s.remote_dma,BX_NE2K_THIS s.remote_bytes));
    retval = chipmem_read(BX_NE2K_THIS s.remote_dma, io_len);
    //
    // The 8390 bumps the address and decreases the byte count
    // by the selected word size after every access, not by
    // the amount of data requested by the host (io_len).
    //
    if (io_len == 4) {
      BX_NE2K_THIS s.remote_dma += io_len;
    } else {
      BX_NE2K_THIS s.remote_dma += (BX_NE2K_THIS s.DCR.wdsize + 1);
    }
    if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) {
      BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8;
    }
    // keep s.remote_bytes from underflowing
    if (BX_NE2K_THIS s.remote_bytes > BX_NE2K_THIS s.DCR.wdsize)
      if (io_len == 4) {
        BX_NE2K_THIS s.remote_bytes -= io_len;
      } else {
        BX_NE2K_THIS s.remote_bytes -= (BX_NE2K_THIS s.DCR.wdsize + 1);
      }
    else
      BX_NE2K_THIS s.remote_bytes = 0;

    // If all bytes have been written, signal remote-DMA complete
    if (BX_NE2K_THIS s.remote_bytes == 0) {
      BX_NE2K_THIS s.ISR.rdma_done = 1;
      if (BX_NE2K_THIS s.IMR.rdma_inte) {
        set_irq_level(1);
      }
    }
    break;

  case 0xf:  // Reset register
    theNE2kDevice->reset(BX_RESET_SOFTWARE);
    break;

  default:
    BX_INFO(("asic read invalid address %04x", (unsigned) offset));
    break;
  }

  return (retval);
}

void bx_ne2k_c::asic_write(Bit32u offset, Bit32u value, unsigned io_len)
{
  BX_DEBUG(("asic write addr=0x%02x, value=0x%04x", (unsigned) offset, (unsigned) value));
  switch (offset) {
  case 0x0:  // Data register - see asic_read for a description

    if ((io_len > 1) && (BX_NE2K_THIS s.DCR.wdsize == 0)) {
      BX_PANIC(("dma write length %d on byte mode operation", io_len));
      break;
    }
    if (BX_NE2K_THIS s.remote_bytes == 0) {
      BX_ERROR(("ne2K: dma write, byte count 0"));
    }

    chipmem_write(BX_NE2K_THIS s.remote_dma, value, io_len);
    if (io_len == 4) {
      BX_NE2K_THIS s.remote_dma += io_len;
    } else {
      BX_NE2K_THIS s.remote_dma += (BX_NE2K_THIS s.DCR.wdsize + 1);
    }
    if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) {
      BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8;
    }

    if (io_len == 4) {
      BX_NE2K_THIS s.remote_bytes -= io_len;
    } else {
      BX_NE2K_THIS s.remote_bytes -= (BX_NE2K_THIS s.DCR.wdsize + 1);
    }
    if (BX_NE2K_THIS s.remote_bytes > BX_NE2K_MEMSIZ)
      BX_NE2K_THIS s.remote_bytes = 0;

    // If all bytes have been written, signal remote-DMA complete
    if (BX_NE2K_THIS s.remote_bytes == 0) {
      BX_NE2K_THIS s.ISR.rdma_done = 1;
      if (BX_NE2K_THIS s.IMR.rdma_inte) {
        set_irq_level(1);
      }
    }
    break;

  case 0xf:  // Reset register
    // end of reset pulse
    break;

  default: // this is invalid, but happens under win95 device detection
    BX_INFO(("asic write invalid address %04x, ignoring", (unsigned) offset));
    break;
  }
}

//
// page0_read/page0_write - These routines handle reads/writes to
// the 'zeroth' page of the DS8390 register file
//
Bit32u bx_ne2k_c::page0_read(Bit32u offset, unsigned int io_len)
{
  Bit8u value = 0;

  if (io_len > 1) {
    BX_ERROR(("bad length! page 0 read from register 0x%02x, len=%u", offset,
              io_len)); /* encountered with win98 hardware probe */
    return value;
  }

  switch (offset) {
  case 0x1:  // CLDA0
    value = (BX_NE2K_THIS s.local_dma & 0xff);
    break;

  case 0x2:  // CLDA1
    value = (BX_NE2K_THIS s.local_dma >> 8);
    break;

  case 0x3:  // BNRY
    value = BX_NE2K_THIS s.bound_ptr;
    break;

  case 0x4:  // TSR
    value = ((BX_NE2K_THIS s.TSR.ow_coll    << 7) |
             (BX_NE2K_THIS s.TSR.cd_hbeat   << 6) |
             (BX_NE2K_THIS s.TSR.fifo_ur    << 5) |
             (BX_NE2K_THIS s.TSR.no_carrier << 4) |
             (BX_NE2K_THIS s.TSR.aborted    << 3) |
             (BX_NE2K_THIS s.TSR.collided   << 2) |
             (BX_NE2K_THIS s.TSR.tx_ok));
    break;

  case 0x5:  // NCR
    value = BX_NE2K_THIS s.num_coll;
    break;

  case 0x6:  // FIFO
    // reading FIFO is only valid in loopback mode
    BX_ERROR(("reading FIFO not supported yet"));
    value = BX_NE2K_THIS s.fifo;
    break;

  case 0x7:  // ISR
    value = ((BX_NE2K_THIS s.ISR.reset     << 7) |
             (BX_NE2K_THIS s.ISR.rdma_done << 6) |
             (BX_NE2K_THIS s.ISR.cnt_oflow << 5) |
             (BX_NE2K_THIS s.ISR.overwrite << 4) |
             (BX_NE2K_THIS s.ISR.tx_err    << 3) |
             (BX_NE2K_THIS s.ISR.rx_err    << 2) |
             (BX_NE2K_THIS s.ISR.pkt_tx    << 1) |
             (BX_NE2K_THIS s.ISR.pkt_rx));
    break;

  case 0x8:  // CRDA0
    value = (BX_NE2K_THIS s.remote_dma & 0xff);
    break;

  case 0x9:  // CRDA1
    value = (BX_NE2K_THIS s.remote_dma >> 8);
    break;

  case 0xa:  // reserved / RTL8029ID0
    if (BX_NE2K_THIS s.pci_enabled) {
      value = 0x50;
    } else {
      BX_INFO(("reserved read - page 0, 0xa"));
      value = 0xff;
    }
    break;

  case 0xb:  // reserved / RTL8029ID1
    if (BX_NE2K_THIS s.pci_enabled) {
      value = 0x43;
    } else {
      BX_INFO(("reserved read - page 0, 0xb"));
      value = 0xff;
    }
    break;

  case 0xc:  // RSR
    value = ((BX_NE2K_THIS s.RSR.deferred    << 7) |
             (BX_NE2K_THIS s.RSR.rx_disabled << 6) |
             (BX_NE2K_THIS s.RSR.rx_mbit     << 5) |
             (BX_NE2K_THIS s.RSR.rx_missed   << 4) |
             (BX_NE2K_THIS s.RSR.fifo_or     << 3) |
             (BX_NE2K_THIS s.RSR.bad_falign  << 2) |
             (BX_NE2K_THIS s.RSR.bad_crc     << 1) |
             (BX_NE2K_THIS s.RSR.rx_ok));
    break;

  case 0xd:  // CNTR0
    value = BX_NE2K_THIS s.tallycnt_0;
    break;

  case 0xe:  // CNTR1
    value = BX_NE2K_THIS s.tallycnt_1;
    break;

  case 0xf:  // CNTR2
    value = BX_NE2K_THIS s.tallycnt_2;
    break;

  default:
    BX_PANIC(("page 0 register 0x%02x out of range", offset));
  }

  BX_DEBUG(("page 0 read from register 0x%02x, value=0x%02x", offset, value));
  return value;
}

void bx_ne2k_c::page0_write(Bit32u offset, Bit32u value, unsigned io_len)
{
  Bit8u value2;

  // It appears to be a common practice to use outw on page0 regs...

  // break up outw into two outb's
  if (io_len == 2) {
    page0_write(offset, (value & 0xff), 1);
    if (offset < 0x0f) {
      page0_write(offset + 1, ((value >> 8) & 0xff), 1);
    }
    return;
  }

  BX_DEBUG(("page 0 write to register 0x%02x, value=0x%02x", offset, value));

  switch (offset) {
  case 0x1:  // PSTART
    BX_NE2K_THIS s.page_start = value;
    break;

  case 0x2:  // PSTOP
    BX_NE2K_THIS s.page_stop = value;
    break;

  case 0x3:  // BNRY
    BX_NE2K_THIS s.bound_ptr = value;
    break;

  case 0x4:  // TPSR
    BX_NE2K_THIS s.tx_page_start = value;
    break;

  case 0x5:  // TBCR0
    // Clear out low byte and re-insert
    BX_NE2K_THIS s.tx_bytes &= 0xff00;
    BX_NE2K_THIS s.tx_bytes |= (value & 0xff);
    break;

  case 0x6:  // TBCR1
    // Clear out high byte and re-insert
    BX_NE2K_THIS s.tx_bytes &= 0x00ff;
    BX_NE2K_THIS s.tx_bytes |= ((value & 0xff) << 8);
    break;

  case 0x7:  // ISR
    value &= 0x7f;  // clear RST bit - status-only bit
    // All other values are cleared iff the ISR bit is 1
    BX_NE2K_THIS s.ISR.pkt_rx    &= ~((bx_bool)((value & 0x01) == 0x01));
    BX_NE2K_THIS s.ISR.pkt_tx    &= ~((bx_bool)((value & 0x02) == 0x02));
    BX_NE2K_THIS s.ISR.rx_err    &= ~((bx_bool)((value & 0x04) == 0x04));
    BX_NE2K_THIS s.ISR.tx_err    &= ~((bx_bool)((value & 0x08) == 0x08));
    BX_NE2K_THIS s.ISR.overwrite &= ~((bx_bool)((value & 0x10) == 0x10));
    BX_NE2K_THIS s.ISR.cnt_oflow &= ~((bx_bool)((value & 0x20) == 0x20));
    BX_NE2K_THIS s.ISR.rdma_done &= ~((bx_bool)((value & 0x40) == 0x40));
    value = ((BX_NE2K_THIS s.ISR.rdma_done << 6) |
             (BX_NE2K_THIS s.ISR.cnt_oflow << 5) |
             (BX_NE2K_THIS s.ISR.overwrite << 4) |
             (BX_NE2K_THIS s.ISR.tx_err    << 3) |
             (BX_NE2K_THIS s.ISR.rx_err    << 2) |
             (BX_NE2K_THIS s.ISR.pkt_tx    << 1) |
             (BX_NE2K_THIS s.ISR.pkt_rx));
    value &= ((BX_NE2K_THIS s.IMR.rdma_inte << 6) |
              (BX_NE2K_THIS s.IMR.cofl_inte << 5) |
              (BX_NE2K_THIS s.IMR.overw_inte << 4) |
              (BX_NE2K_THIS s.IMR.txerr_inte << 3) |
              (BX_NE2K_THIS s.IMR.rxerr_inte << 2) |
              (BX_NE2K_THIS s.IMR.tx_inte << 1) |
              (BX_NE2K_THIS s.IMR.rx_inte));
    if (value == 0)
      set_irq_level(0);
    break;

  case 0x8:  // RSAR0
    // Clear out low byte and re-insert
    BX_NE2K_THIS s.remote_start &= 0xff00;
    BX_NE2K_THIS s.remote_start |= (value & 0xff);
    BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start;
    break;

  case 0x9:  // RSAR1
    // Clear out high byte and re-insert
    BX_NE2K_THIS s.remote_start &= 0x00ff;
    BX_NE2K_THIS s.remote_start |= ((value & 0xff) << 8);
    BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start;
    break;

  case 0xa:  // RBCR0
    // Clear out low byte and re-insert
    BX_NE2K_THIS s.remote_bytes &= 0xff00;
    BX_NE2K_THIS s.remote_bytes |= (value & 0xff);
    break;

  case 0xb:  // RBCR1
    // Clear out high byte and re-insert
    BX_NE2K_THIS s.remote_bytes &= 0x00ff;
    BX_NE2K_THIS s.remote_bytes |= ((value & 0xff) << 8);
    break;

  case 0xc:  // RCR
    // Check if the reserved bits are set
    if (value & 0xc0)
      BX_INFO(("RCR write, reserved bits set"));

    // Set all other bit-fields
    BX_NE2K_THIS s.RCR.errors_ok = ((value & 0x01) == 0x01);
    BX_NE2K_THIS s.RCR.runts_ok  = ((value & 0x02) == 0x02);
    BX_NE2K_THIS s.RCR.broadcast = ((value & 0x04) == 0x04);
    BX_NE2K_THIS s.RCR.multicast = ((value & 0x08) == 0x08);
    BX_NE2K_THIS s.RCR.promisc   = ((value & 0x10) == 0x10);
    BX_NE2K_THIS s.RCR.monitor   = ((value & 0x20) == 0x20);

    // Monitor bit is a little suspicious...
    if (value & 0x20)
      BX_INFO(("RCR write, monitor bit set!"));
    break;

  case 0xd:  // TCR
    // Check reserved bits
    if (value & 0xe0)
      BX_ERROR(("TCR write, reserved bits set"));

    // Test loop mode (not supported)
    if (value & 0x06) {
      BX_NE2K_THIS s.TCR.loop_cntl = (value & 0x6) >> 1;
      BX_INFO(("TCR write, loop mode %d not supported", BX_NE2K_THIS s.TCR.loop_cntl));
    } else {
      BX_NE2K_THIS s.TCR.loop_cntl = 0;
    }

    // Inhibit-CRC not supported.
    if (value & 0x01)
      BX_PANIC(("TCR write, inhibit-CRC not supported"));

    // Auto-transmit disable very suspicious
    if (value & 0x08)
      BX_PANIC(("TCR write, auto transmit disable not supported"));

    // Allow collision-offset to be set, although not used
    BX_NE2K_THIS s.TCR.coll_prio = ((value & 0x08) == 0x08);
    break;

  case 0xe:  // DCR
    // the loopback mode is not suppported yet
    if (!(value & 0x08)) {
      BX_ERROR(("DCR write, loopback mode selected"));
    }
    // It is questionable to set longaddr and auto_rx, since they
    // aren't supported on the ne2000. Print a warning and continue
    if (value & 0x04)
      BX_INFO(("DCR write - LAS set ???"));
    if (value & 0x10)
      BX_INFO(("DCR write - AR set ???"));

    // Set other values.
    BX_NE2K_THIS s.DCR.wdsize   = ((value & 0x01) == 0x01);
    BX_NE2K_THIS s.DCR.endian   = ((value & 0x02) == 0x02);
    BX_NE2K_THIS s.DCR.longaddr = ((value & 0x04) == 0x04); // illegal ?
    BX_NE2K_THIS s.DCR.loop     = ((value & 0x08) == 0x08);
    BX_NE2K_THIS s.DCR.auto_rx  = ((value & 0x10) == 0x10); // also illegal ?
    BX_NE2K_THIS s.DCR.fifo_size = (value & 0x50) >> 5;
    break;

  case 0xf:  // IMR
    // Check for reserved bit
    if (value & 0x80)
      BX_ERROR(("IMR write, reserved bit set"));

    // Set other values
    BX_NE2K_THIS s.IMR.rx_inte    = ((value & 0x01) == 0x01);
    BX_NE2K_THIS s.IMR.tx_inte    = ((value & 0x02) == 0x02);
    BX_NE2K_THIS s.IMR.rxerr_inte = ((value & 0x04) == 0x04);
    BX_NE2K_THIS s.IMR.txerr_inte = ((value & 0x08) == 0x08);
    BX_NE2K_THIS s.IMR.overw_inte = ((value & 0x10) == 0x10);
    BX_NE2K_THIS s.IMR.cofl_inte  = ((value & 0x20) == 0x20);
    BX_NE2K_THIS s.IMR.rdma_inte  = ((value & 0x40) == 0x40);
    value2 = ((BX_NE2K_THIS s.ISR.rdma_done << 6) |
              (BX_NE2K_THIS s.ISR.cnt_oflow << 5) |
              (BX_NE2K_THIS s.ISR.overwrite << 4) |
              (BX_NE2K_THIS s.ISR.tx_err    << 3) |
              (BX_NE2K_THIS s.ISR.rx_err    << 2) |
              (BX_NE2K_THIS s.ISR.pkt_tx    << 1) |
              (BX_NE2K_THIS s.ISR.pkt_rx));
    if (((value & value2) & 0x7f) == 0) {
      set_irq_level(0);
    } else {
      set_irq_level(1);
    }
    break;

  default:
    BX_PANIC(("page 0 write, bad register 0x%02x", offset));
  }
}

//
// page1_read/page1_write - These routines handle reads/writes to
// the first page of the DS8390 register file
//
Bit32u bx_ne2k_c::page1_read(Bit32u offset, unsigned int io_len)
{
  BX_DEBUG(("page 1 read from register 0x%02x, len=%u", offset, io_len));

  if (io_len > 1)
    BX_PANIC(("bad length! page 1 read from register 0x%02x, len=%u", offset, io_len));

  switch (offset) {
  case 0x1:  // PAR0-5
  case 0x2:
  case 0x3:
  case 0x4:
  case 0x5:
  case 0x6:
    return (BX_NE2K_THIS s.physaddr[offset - 1]);
    break;

  case 0x7:  // CURR
    BX_DEBUG(("returning current page: 0x%02x", (BX_NE2K_THIS s.curr_page)));
    return (BX_NE2K_THIS s.curr_page);

  case 0x8:  // MAR0-7
  case 0x9:
  case 0xa:
  case 0xb:
  case 0xc:
  case 0xd:
  case 0xe:
  case 0xf:
    return (BX_NE2K_THIS s.mchash[offset - 8]);
    break;

  default:
    BX_PANIC(("page 1 read register 0x%02x out of range", offset));
  }

  return (0);
}

void bx_ne2k_c::page1_write(Bit32u offset, Bit32u value, unsigned io_len)
{
  BX_DEBUG(("page 1 write to register 0x%02x, len=%u, value=0x%04x", offset,
            io_len, value));

  switch (offset) {
  case 0x1:  // PAR0-5
  case 0x2:
  case 0x3:
  case 0x4:
  case 0x5:
  case 0x6:
    BX_NE2K_THIS s.physaddr[offset - 1] = value;
    if (offset == 6) {
      BX_INFO(("Physical address set to %02x:%02x:%02x:%02x:%02x:%02x",
               BX_NE2K_THIS s.physaddr[0],
               BX_NE2K_THIS s.physaddr[1],
               BX_NE2K_THIS s.physaddr[2],
               BX_NE2K_THIS s.physaddr[3],
               BX_NE2K_THIS s.physaddr[4],
               BX_NE2K_THIS s.physaddr[5]));
    }
    break;

  case 0x7:  // CURR
    BX_NE2K_THIS s.curr_page = value;
    break;

  case 0x8:  // MAR0-7
  case 0x9:
  case 0xa:
  case 0xb:
  case 0xc:
  case 0xd:
  case 0xe:
  case 0xf:
    BX_NE2K_THIS s.mchash[offset - 8] = value;
    break;

  default:
    BX_PANIC(("page 1 write register 0x%02x out of range", offset));
  }
}

//
// page2_read/page2_write - These routines handle reads/writes to
// the second page of the DS8390 register file
//
Bit32u bx_ne2k_c::page2_read(Bit32u offset, unsigned int io_len)
{
  BX_DEBUG(("page 2 read from register 0x%02x, len=%u", offset, io_len));

  if (io_len > 1)
    BX_PANIC(("bad length!  page 2 read from register 0x%02x, len=%u", offset, io_len));

  switch (offset) {
  case 0x1:  // PSTART
    return (BX_NE2K_THIS s.page_start);

  case 0x2:  // PSTOP
    return (BX_NE2K_THIS s.page_stop);

  case 0x3:  // Remote Next-packet pointer
    return (BX_NE2K_THIS s.rempkt_ptr);

  case 0x4:  // TPSR
    return (BX_NE2K_THIS s.tx_page_start);

  case 0x5:  // Local Next-packet pointer
    return (BX_NE2K_THIS s.localpkt_ptr);

  case 0x6:  // Address counter (upper)
    return (BX_NE2K_THIS s.address_cnt >> 8);

  case 0x7:  // Address counter (lower)
    return (BX_NE2K_THIS s.address_cnt & 0xff);

  case 0x8:  // Reserved
  case 0x9:
  case 0xa:
  case 0xb:
    BX_ERROR(("reserved read - page 2, register 0x%02x", offset));
    return (0xff);

  case 0xc:  // RCR
    return ((BX_NE2K_THIS s.RCR.monitor   << 5) |
	    (BX_NE2K_THIS s.RCR.promisc   << 4) |
	    (BX_NE2K_THIS s.RCR.multicast << 3) |
	    (BX_NE2K_THIS s.RCR.broadcast << 2) |
	    (BX_NE2K_THIS s.RCR.runts_ok  << 1) |
	    (BX_NE2K_THIS s.RCR.errors_ok));

  case 0xd:  // TCR
    return ((BX_NE2K_THIS s.TCR.coll_prio   << 4) |
	    (BX_NE2K_THIS s.TCR.ext_stoptx  << 3) |
	    ((BX_NE2K_THIS s.TCR.loop_cntl & 0x3) << 1) |
	    (BX_NE2K_THIS s.TCR.crc_disable));

  case 0xe:  // DCR
    return (((BX_NE2K_THIS s.DCR.fifo_size & 0x3) << 5) |
	    (BX_NE2K_THIS s.DCR.auto_rx  << 4) |
	    (BX_NE2K_THIS s.DCR.loop     << 3) |
	    (BX_NE2K_THIS s.DCR.longaddr << 2) |
	    (BX_NE2K_THIS s.DCR.endian   << 1) |
	    (BX_NE2K_THIS s.DCR.wdsize));

  case 0xf:  // IMR
    return ((BX_NE2K_THIS s.IMR.rdma_inte  << 6) |
	    (BX_NE2K_THIS s.IMR.cofl_inte  << 5) |
	    (BX_NE2K_THIS s.IMR.overw_inte << 4) |
	    (BX_NE2K_THIS s.IMR.txerr_inte << 3) |
	    (BX_NE2K_THIS s.IMR.rxerr_inte << 2) |
	    (BX_NE2K_THIS s.IMR.tx_inte    << 1) |
	    (BX_NE2K_THIS s.IMR.rx_inte));

  default:
    BX_PANIC(("page 2 register 0x%02x out of range", offset));
  }

  return (0);
}

void bx_ne2k_c::page2_write(Bit32u offset, Bit32u value, unsigned io_len)
{
  // Maybe all writes here should be BX_PANIC()'d, since they
  // affect internal operation, but let them through for now
  // and print a warning.
  BX_ERROR(("page 2 write to register 0x%02x, len=%u, value=0x%04x", offset,
            io_len, value));

  switch (offset) {
  case 0x1:  // CLDA0
    // Clear out low byte and re-insert
    BX_NE2K_THIS s.local_dma &= 0xff00;
    BX_NE2K_THIS s.local_dma |= (value & 0xff);
    break;

  case 0x2:  // CLDA1
    // Clear out high byte and re-insert
    BX_NE2K_THIS s.local_dma &= 0x00ff;
    BX_NE2K_THIS s.local_dma |= ((value & 0xff) << 8);
    break;

  case 0x3:  // Remote Next-pkt pointer
    BX_NE2K_THIS s.rempkt_ptr = value;
    break;

  case 0x4:
    BX_PANIC(("page 2 write to reserved register 0x04"));
    break;

  case 0x5:  // Local Next-packet pointer
    BX_NE2K_THIS s.localpkt_ptr = value;
    break;

  case 0x6:  // Address counter (upper)
    // Clear out high byte and re-insert
    BX_NE2K_THIS s.address_cnt &= 0x00ff;
    BX_NE2K_THIS s.address_cnt |= ((value & 0xff) << 8);
    break;

  case 0x7:  // Address counter (lower)
    // Clear out low byte and re-insert
    BX_NE2K_THIS s.address_cnt &= 0xff00;
    BX_NE2K_THIS s.address_cnt |= (value & 0xff);
    break;

  case 0x8:
  case 0x9:
  case 0xa:
  case 0xb:
  case 0xc:
  case 0xd:
  case 0xe:
  case 0xf:
    BX_PANIC(("page 2 write to reserved register 0x%02x", offset));
    break;

  default:
    BX_PANIC(("page 2 write, illegal register 0x%02x", offset));
    break;
  }
}

//
// page3_read/page3_write - writes to this page are illegal
//
Bit32u bx_ne2k_c::page3_read(Bit32u offset, unsigned int io_len)
{
  if (BX_NE2K_THIS s.pci_enabled) {
    switch (offset) {
      case 0x3:  // CONFIG0
        return (0);
      case 0x5:  // CONFIG2
        return (0x40);
      case 0x6:  // CONFIG3
        return (0x40);
      default:
        BX_ERROR(("page 3 read register 0x%02x attempted", offset));
        return (0);
    }
  } else {
    BX_ERROR(("page 3 read register 0x%02x attempted", offset));
    return (0);
  }
}

void bx_ne2k_c::page3_write(Bit32u offset, Bit32u value, unsigned io_len)
{
  BX_ERROR(("page 3 write register 0x%02x attempted", offset));
}

//
// tx_timer_handler/tx_timer
//
void bx_ne2k_c::tx_timer_handler(void *this_ptr)
{
  bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;
  class_ptr->tx_timer();
}

void bx_ne2k_c::tx_timer(void)
{
  BX_DEBUG(("tx_timer"));
  BX_NE2K_THIS s.CR.tx_packet = 0;
  BX_NE2K_THIS s.TSR.tx_ok = 1;
  BX_NE2K_THIS s.ISR.pkt_tx = 1;
  // Generate an interrupt if not masked
  if (BX_NE2K_THIS s.IMR.tx_inte) {
    set_irq_level(1);
  }
  BX_NE2K_THIS s.tx_timer_active = 0;
}


#if BX_SUPPORT_PCI
bx_bool bx_ne2k_c::mem_read_handler(bx_phy_address addr, unsigned len,
                                    void *data, void *param)
{
  Bit8u  *data_ptr;

  Bit32u mask = (BX_NE2K_THIS pci_rom_size - 1);
#ifdef BX_LITTLE_ENDIAN
  data_ptr = (Bit8u *) data;
#else // BX_BIG_ENDIAN
  data_ptr = (Bit8u *) data + (len - 1);
#endif
  for (unsigned i = 0; i < len; i++) {
    if (BX_NE2K_THIS pci_conf[0x30] & 0x01) {
      *data_ptr = BX_NE2K_THIS pci_rom[addr & mask];
    } else {
      *data_ptr = 0xff;
    }
    addr++;
#ifdef BX_LITTLE_ENDIAN
    data_ptr++;
#else // BX_BIG_ENDIAN
    data_ptr--;
#endif
  }
  return 1;
}
#endif

//
// read_handler/read - i/o 'catcher' function called from BOCHS
// mainline when the CPU attempts a read in the i/o space registered
// by this ne2000 instance
//
Bit32u bx_ne2k_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_NE2K_SMF
  bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;

  return class_ptr->read(address, io_len);
}

Bit32u bx_ne2k_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_NE2K_SMF
  BX_DEBUG(("read addr %x, len %d", address, io_len));
  Bit32u retval = 0;
  int offset = address - BX_NE2K_THIS s.base_address;

  if (offset >= 0x10) {
    retval = asic_read(offset - 0x10, io_len);
  } else if (offset == 0x00) {
    retval = read_cr();
  } else {
    switch (BX_NE2K_THIS s.CR.pgsel) {
    case 0x00:
      retval = page0_read(offset, io_len);
      break;

    case 0x01:
      retval = page1_read(offset, io_len);
      break;

    case 0x02:
      retval = page2_read(offset, io_len);
      break;

    case 0x03:
      retval = page3_read(offset, io_len);
      break;

    default:
      BX_PANIC(("ne2K: unknown value of pgsel in read - %d",
	       BX_NE2K_THIS s.CR.pgsel));
    }
  }

  return (retval);
}

//
// write_handler/write - i/o 'catcher' function called from BOCHS
// mainline when the CPU attempts a write in the i/o space registered
// by this ne2000 instance
//
void bx_ne2k_c::write_handler(void *this_ptr, Bit32u address, Bit32u value,
			 unsigned io_len)
{
#if !BX_USE_NE2K_SMF
  bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;
  class_ptr->write(address, value, io_len);
}

void bx_ne2k_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_NE2K_SMF
  BX_DEBUG(("write addr %x, value %x len %d", address, value, io_len));
  int offset = address - BX_NE2K_THIS s.base_address;

  //
  // The high 16 bytes of i/o space are for the ne2000 asic -
  //  the low 16 bytes are for the DS8390, with the current
  //  page being selected by the PS0,PS1 registers in the
  //  command register
  //
  if (offset >= 0x10) {
    asic_write(offset - 0x10, value, io_len);
  } else if (offset == 0x00) {
    write_cr(value);
  } else {
    switch (BX_NE2K_THIS s.CR.pgsel) {
    case 0x00:
      page0_write(offset, value, io_len);
      break;

    case 0x01:
      page1_write(offset, value, io_len);
      break;

    case 0x02:
      page2_write(offset, value, io_len);
      break;

    case 0x03:
      page3_write(offset, value, io_len);
      break;

    default:
      BX_PANIC(("ne2K: unknown value of pgsel in write - %d",
	       BX_NE2K_THIS s.CR.pgsel));
    }
  }
}


/*
 * mcast_index() - return the 6-bit index into the multicast
 * table. Stolen unashamedly from FreeBSD's if_ed.c
 */
unsigned bx_ne2k_c::mcast_index(const void *dst)
{
#define POLYNOMIAL 0x04c11db6
  Bit32u crc = 0xffffffffL;
  int carry, i, j;
  unsigned char b;
  unsigned char *ep = (unsigned char *) dst;

  for (i = 6; --i >= 0;) {
    b = *ep++;
    for (j = 8; --j >= 0;) {
      carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01);
      crc <<= 1;
      b >>= 1;
      if (carry)
	crc = ((crc ^ POLYNOMIAL) | carry);
    }
  }
  return (crc >> 26);
#undef POLYNOMIAL
}

/*
 * Callback from the eth system driver to check if the device can receive
 */
Bit32u bx_ne2k_c::rx_status_handler(void *arg)
{
  bx_ne2k_c *class_ptr = (bx_ne2k_c *) arg;
  return class_ptr->rx_status();
}

Bit32u bx_ne2k_c::rx_status()
{
  Bit32u status = BX_NETDEV_10MBIT;
  if ((BX_NE2K_THIS s.CR.stop == 0) &&
      (BX_NE2K_THIS s.page_start != 0) &&
      ((BX_NE2K_THIS s.DCR.loop != 0) ||
       (BX_NE2K_THIS s.TCR.loop_cntl == 0))) {
    status |= BX_NETDEV_RXREADY;
  }
  return status;
}

/*
 * Callback from the eth system driver when a frame has arrived
 */
void bx_ne2k_c::rx_handler(void *arg, const void *buf, unsigned len)
{
    // BX_DEBUG(("rx_handler with length %d", len));
  bx_ne2k_c *class_ptr = (bx_ne2k_c *) arg;

  class_ptr->rx_frame(buf, len);
}

/*
 * rx_frame() - called by the platform-specific code when an
 * ethernet frame has been received. The destination address
 * is tested to see if it should be accepted, and if the
 * rx ring has enough room, it is copied into it and
 * the receive process is updated
 */
void bx_ne2k_c::rx_frame(const void *buf, unsigned io_len)
{
  int pages;
  int avail;
  unsigned idx;
//int wrapped;
  int nextpage;
  unsigned char pkthdr[4];
  unsigned char *pktbuf = (unsigned char *) buf;
  unsigned char *startptr;

  BX_DEBUG(("rx_frame with length %d", io_len));


  if ((BX_NE2K_THIS s.CR.stop != 0) ||
      (BX_NE2K_THIS s.page_start == 0) ||
      ((BX_NE2K_THIS s.DCR.loop == 0) &&
       (BX_NE2K_THIS s.TCR.loop_cntl != 0))) {

    return;
  }

  // Add the pkt header + CRC to the length, and work
  // out how many 256-byte pages the frame would occupy
  pages = (io_len + 4 + 4 + 255)/256;

  if (BX_NE2K_THIS s.curr_page < BX_NE2K_THIS s.bound_ptr) {
    avail = BX_NE2K_THIS s.bound_ptr - BX_NE2K_THIS s.curr_page;
  } else {
    avail = (BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start) -
      (BX_NE2K_THIS s.curr_page - BX_NE2K_THIS s.bound_ptr);
//  wrapped = 1;
  }

  // Avoid getting into a buffer overflow condition by not attempting
  // to do partial receives. The emulation to handle this condition
  // seems particularly painful.
  if ((avail < pages)
#if BX_NE2K_NEVER_FULL_RING
      || (avail == pages)
#endif
      ) {
    return;
  }

  if ((io_len < 60) && !BX_NE2K_THIS s.RCR.runts_ok) {
    BX_DEBUG(("rejected small packet, length %d", io_len));
    return;
  }

  // Do address filtering if not in promiscuous mode
  if (! BX_NE2K_THIS s.RCR.promisc) {
    if (!memcmp(buf, broadcast_macaddr, 6)) {
      if (!BX_NE2K_THIS s.RCR.broadcast) {
	return;
      }
    } else if (pktbuf[0] & 0x01) {
	if (! BX_NE2K_THIS s.RCR.multicast) {
	    return;
	}
      idx = mcast_index(buf);
      if (!(BX_NE2K_THIS s.mchash[idx >> 3] & (1 << (idx & 0x7)))) {
	return;
      }
    } else if (0 != memcmp(buf, BX_NE2K_THIS s.physaddr, 6)) {
      return;
    }
  } else {
      BX_DEBUG(("rx_frame promiscuous receive"));
  }

//    BX_INFO(("rx_frame %d to %x:%x:%x:%x:%x:%x from %x:%x:%x:%x:%x:%x",
//  	   io_len,
//  	   pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3], pktbuf[4], pktbuf[5],
//  	   pktbuf[6], pktbuf[7], pktbuf[8], pktbuf[9], pktbuf[10], pktbuf[11]));

  nextpage = BX_NE2K_THIS s.curr_page + pages;
  if (nextpage >= BX_NE2K_THIS s.page_stop) {
    nextpage -= BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start;
  }

  // Setup packet header
  pkthdr[0] = 0;	// rx status - old behavior
  pkthdr[0] = 1;        // Probably better to set it all the time
                        // rather than set it to 0, which is clearly wrong.
  if (pktbuf[0] & 0x01) {
    pkthdr[0] |= 0x20;  // rx status += multicast packet
  }
  pkthdr[1] = nextpage;	// ptr to next packet
  pkthdr[2] = (io_len + 4) & 0xff;	// length-low
  pkthdr[3] = (io_len + 4) >> 8;	// length-hi

  // copy into buffer, update curpage, and signal interrupt if config'd
  startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.curr_page * 256 -
			       BX_NE2K_MEMSTART];
  if ((nextpage > BX_NE2K_THIS s.curr_page) ||
      ((BX_NE2K_THIS s.curr_page + pages) == BX_NE2K_THIS s.page_stop)) {
    memcpy(startptr, pkthdr, 4);
    memcpy(startptr + 4, buf, io_len);
    BX_NE2K_THIS s.curr_page = nextpage;
  } else {
    int endbytes = (BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.curr_page)
      * 256;
    memcpy(startptr, pkthdr, 4);
    memcpy(startptr + 4, buf, endbytes - 4);
    startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.page_start * 256 -
				 BX_NE2K_MEMSTART];
    memcpy(startptr, (void *)(pktbuf + endbytes - 4),
	   io_len - endbytes + 8);
    BX_NE2K_THIS s.curr_page = nextpage;
  }

  BX_NE2K_THIS s.RSR.rx_ok = 1;
  BX_NE2K_THIS s.RSR.rx_mbit = (bx_bool)((pktbuf[0] & 0x01) > 0);

  BX_NE2K_THIS s.ISR.pkt_rx = 1;

  if (BX_NE2K_THIS s.IMR.rx_inte) {
    set_irq_level(1);
  }

  bx_gui->statusbar_setitem(BX_NE2K_THIS s.statusbar_id, 1);
}

void bx_ne2k_c::set_irq_level(bx_bool level)
{
  if (BX_NE2K_THIS s.pci_enabled) {
#if BX_SUPPORT_PCI
    DEV_pci_set_irq(BX_NE2K_THIS s.devfunc, BX_NE2K_THIS pci_conf[0x3d], level);
#endif
  } else {
    if (level) {
      DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
    } else {
      DEV_pic_lower_irq(BX_NE2K_THIS s.base_irq);
    }
  }
}

#if BX_SUPPORT_PCI
// pci configuration space write callback handler
void bx_ne2k_c::pci_write_handler(Bit8u address, Bit32u value, unsigned io_len)
{
  Bit8u value8, oldval;

  if ((address > 0x13) && (address < 0x30))
    return;

  BX_DEBUG_PCI_WRITE(address, value, io_len);
  for (unsigned i=0; i<io_len; i++) {
    oldval = BX_NE2K_THIS pci_conf[address+i];
    value8 = (value >> (i*8)) & 0xFF;
    switch (address+i) {
      case 0x04:
        value8 &= 0x03;
        break;
      default:
        value8 = oldval;
    }
    BX_NE2K_THIS pci_conf[address+i] = value8;
  }
}

void bx_ne2k_c::pci_bar_change_notify(void)
{
  BX_NE2K_THIS s.base_address = pci_bar[0].addr;
}
#endif /* BX_SUPPORT_PCI */

#if BX_DEBUGGER
void bx_ne2k_c::debug_dump(int argc, char **argv)
{
  int page = -1, reg = -1;

  for (int arg = 0; arg < argc; arg++) {
    if (!strncmp(argv[arg], "page=", 5) && isdigit(argv[arg][5])) {
      page = atoi(&argv[arg][5]);
    } else if (!strncmp(argv[arg], "reg=", 4) && isdigit(argv[arg][4])) {
      reg = atoi(&argv[arg][4]);
    } else {
      dbg_printf("\nUnknown option: '%s'\n", argv[arg]);
      return;
    }
  }
  BX_NE2K_THIS print_info(page, reg, 0);
}

/*
 * this implements the info device 'ne2k' command in the debugger.
 * info device 'ne2k' - shows all registers
 * info device 'ne2k' 'page=N' - shows all registers in a page
 * info device 'ne2k' 'page=N,reg=M' - shows just one register
 */

#define SHOW_FIELD(reg,field) do { \
  if (n>0 && !(n%5)) dbg_printf ("\n  "); \
  dbg_printf ("%s=%d ", #field, BX_NE2K_THIS s.reg.field); \
  n++; \
} while (0);
#define BX_HIGH_BYTE(x) ((0xff00 & (x)) >> 8)
#define BX_LOW_BYTE(x) (0x00ff & (x))
#define BX_DUPLICATE(n) if (brief && num!=n) break;

void bx_ne2k_c::print_info(int page, int reg, int brief)
{
  int i;
  int n = 0;
  if (page < 0) {
    for (page=0; page<=2; page++)
      theNE2kDevice->print_info(page, reg, 1);
    // tell them how to use this command
    dbg_printf("\nSupported options:\n");
    dbg_printf("info device 'ne2k' 'page=N' - show registers in page N\n");
    dbg_printf("info device 'ne2k' 'page=N,reg=M' - show just one register\n");
    return;
  }
  if (page > 2) {
    dbg_printf("NE2K has only pages 0, 1, and 2.  Page %d is out of range.\n", page);
    return;
  }
  if (reg < 0) {
    dbg_printf("NE2K registers, page %d\n", page);
    dbg_printf("----------------------\n");
    for (reg=0; reg<=15; reg++)
      theNE2kDevice->print_info(page, reg, 1);
    dbg_printf("----------------------\n");
    return;
  }
  if (reg > 15) {
    dbg_printf("NE2K has only registers 0-15 (0x0-0xf).  Register %d is out of range.\n", reg);
    return;
  }
  if (!brief) {
    dbg_printf("NE2K Info - page %d, register 0x%02x\n", page, reg);
    dbg_printf("----------------------------------\n");
  }
  int num = page*0x100 + reg;
  switch (num) {
    case 0x0000:
    case 0x0100:
    case 0x0200:
      dbg_printf ("CR (Command register):\n  ");
      SHOW_FIELD(CR, stop);
      SHOW_FIELD(CR, start);
      SHOW_FIELD(CR, tx_packet);
      SHOW_FIELD(CR, rdma_cmd);
      SHOW_FIELD(CR, pgsel);
      dbg_printf("\n");
      break;
    case 0x0003:
      dbg_printf("BNRY = Boundary Pointer = 0x%02x\n", BX_NE2K_THIS s.bound_ptr);
      break;
    case 0x0004:
      dbg_printf("TSR (Transmit Status Register), read-only:\n  ");
      SHOW_FIELD(TSR, tx_ok);
      SHOW_FIELD(TSR, reserved);
      SHOW_FIELD(TSR, collided);
      SHOW_FIELD(TSR, aborted);
      SHOW_FIELD(TSR, no_carrier);
      SHOW_FIELD(TSR, fifo_ur);
      SHOW_FIELD(TSR, cd_hbeat);
      SHOW_FIELD(TSR, ow_coll);
      dbg_printf("\n");
      // fall through into TPSR, no break line.
    case 0x0204:
      dbg_printf("TPSR = Transmit Page Start = 0x%02x\n", BX_NE2K_THIS s.tx_page_start);
      break;
    case 0x0005:
    case 0x0006:  BX_DUPLICATE(0x0005);
      dbg_printf("NCR = Number of Collisions Register (read-only) = 0x%02x\n", BX_NE2K_THIS s.num_coll);
      dbg_printf("TBCR1,TBCR0 = Transmit Byte Count = %02x %02x\n",
          BX_HIGH_BYTE(BX_NE2K_THIS s.tx_bytes),
          BX_LOW_BYTE(BX_NE2K_THIS s.tx_bytes));
      dbg_printf("FIFO = %02x\n", BX_NE2K_THIS s.fifo);
      break;
    case 0x0007:
      dbg_printf("ISR (Interrupt Status Register):\n  ");
      SHOW_FIELD(ISR, pkt_rx);
      SHOW_FIELD(ISR, pkt_tx);
      SHOW_FIELD(ISR, rx_err);
      SHOW_FIELD(ISR, tx_err);
      SHOW_FIELD(ISR, overwrite);
      SHOW_FIELD(ISR, cnt_oflow);
      SHOW_FIELD(ISR, rdma_done);
      SHOW_FIELD(ISR, reset);
      dbg_printf("\n");
      break;
    case 0x0008:
    case 0x0009:  BX_DUPLICATE(0x0008);
      dbg_printf("CRDA1,0 = Current remote DMA address = %02x %02x\n",
          BX_HIGH_BYTE(BX_NE2K_THIS s.remote_dma),
          BX_LOW_BYTE(BX_NE2K_THIS s.remote_dma));
      dbg_printf("RSAR1,0 = Remote start address = %02x %02x\n",
          BX_HIGH_BYTE(s.remote_start),
          BX_LOW_BYTE(s.remote_start));
      break;
    case 0x000a:
    case 0x000b:  BX_DUPLICATE(0x000a);
      dbg_printf("RCBR1,0 = Remote byte count = %02x\n", BX_NE2K_THIS s.remote_bytes);
      break;
    case 0x000c:
      dbg_printf("RSR (Receive Status Register), read-only:\n  ");
      SHOW_FIELD(RSR, rx_ok);
      SHOW_FIELD(RSR, bad_crc);
      SHOW_FIELD(RSR, bad_falign);
      SHOW_FIELD(RSR, fifo_or);
      SHOW_FIELD(RSR, rx_missed);
      SHOW_FIELD(RSR, rx_mbit);
      SHOW_FIELD(RSR, rx_disabled);
      SHOW_FIELD(RSR, deferred);
      dbg_printf("\n");
      // fall through into RCR
    case 0x020c:
      dbg_printf("RCR (Receive Configuration Register):\n  ");
      SHOW_FIELD(RCR, errors_ok);
      SHOW_FIELD(RCR, runts_ok);
      SHOW_FIELD(RCR, broadcast);
      SHOW_FIELD(RCR, multicast);
      SHOW_FIELD(RCR, promisc);
      SHOW_FIELD(RCR, monitor);
      SHOW_FIELD(RCR, reserved);
      dbg_printf("\n");
      break;
    case 0x000d:
      dbg_printf("CNTR0 = Tally Counter 0 (Frame alignment errors) = %02x\n",
        BX_NE2K_THIS s.tallycnt_0);
      // fall through into TCR
    case 0x020d:
      dbg_printf("TCR (Transmit Configuration Register):\n  ");
      SHOW_FIELD(TCR, crc_disable);
      SHOW_FIELD(TCR, loop_cntl);
      SHOW_FIELD(TCR, ext_stoptx);
      SHOW_FIELD(TCR, coll_prio);
      SHOW_FIELD(TCR, reserved);
      dbg_printf("\n");
      break;
    case 0x000e:
      dbg_printf("CNTR1 = Tally Counter 1 (CRC Errors) = %02x\n",
        BX_NE2K_THIS s.tallycnt_1);
      // fall through into DCR
    case 0x020e:
      dbg_printf("DCR (Data Configuration Register):\n  ");
      SHOW_FIELD(DCR, wdsize);
      SHOW_FIELD(DCR, endian);
      SHOW_FIELD(DCR, longaddr);
      SHOW_FIELD(DCR, loop);
      SHOW_FIELD(DCR, auto_rx);
      SHOW_FIELD(DCR, fifo_size);
      dbg_printf("\n");
      break;
    case 0x000f:
      dbg_printf("CNTR2 = Tally Counter 2 (Missed Packet Errors) = %02x\n",
        BX_NE2K_THIS s.tallycnt_2);
      // fall through into IMR
    case 0x020f:
      dbg_printf("IMR (Interrupt Mask Register)\n  ");
      SHOW_FIELD(IMR, rx_inte);
      SHOW_FIELD(IMR, tx_inte);
      SHOW_FIELD(IMR, rxerr_inte);
      SHOW_FIELD(IMR, txerr_inte);
      SHOW_FIELD(IMR, overw_inte);
      SHOW_FIELD(IMR, cofl_inte);
      SHOW_FIELD(IMR, rdma_inte);
      SHOW_FIELD(IMR, reserved);
      dbg_printf("\n");
      break;
    case 0x0101:
    case 0x0102:  BX_DUPLICATE(0x0101);
    case 0x0103:  BX_DUPLICATE(0x0101);
    case 0x0104:  BX_DUPLICATE(0x0101);
    case 0x0105:  BX_DUPLICATE(0x0101);
    case 0x0106:  BX_DUPLICATE(0x0101);
      dbg_printf("MAC address registers are located at page 1, registers 1-6.\n");
      dbg_printf("The MAC address is ");
      for (i=0; i<=5; i++)
        dbg_printf("%02x%c", BX_NE2K_THIS s.physaddr[i], i<5?':' : '\n');
      break;
    case 0x0107:
      dbg_printf("Current page is 0x%02x\n", BX_NE2K_THIS s.curr_page);
      break;
    case 0x0108:
    case 0x0109:  BX_DUPLICATE(0x0108);
    case 0x010A:  BX_DUPLICATE(0x0108);
    case 0x010B:  BX_DUPLICATE(0x0108);
    case 0x010C:  BX_DUPLICATE(0x0108);
    case 0x010D:  BX_DUPLICATE(0x0108);
    case 0x010E:  BX_DUPLICATE(0x0108);
    case 0x010F:  BX_DUPLICATE(0x0108);
      dbg_printf("MAR0-7 (Multicast address registers 0-7) are set to:\n");
      for (i=0; i<8; i++) dbg_printf("%02x ", BX_NE2K_THIS s.mchash[i]);
      dbg_printf("\nMAR0 is listed first.\n");
      break;
    case 0x0001:
    case 0x0002:  BX_DUPLICATE(0x0001);
    case 0x0201:  BX_DUPLICATE(0x0001);
    case 0x0202:  BX_DUPLICATE(0x0001);
      dbg_printf("PSTART = Page start register = %02x\n", BX_NE2K_THIS s.page_start);
      dbg_printf("PSTOP = Page stop register = %02x\n", BX_NE2K_THIS s.page_stop);
      dbg_printf("Local DMA address = %02x %02x\n",
          BX_HIGH_BYTE(BX_NE2K_THIS s.local_dma),
          BX_LOW_BYTE(BX_NE2K_THIS s.local_dma));
      break;
    case 0x0203:
      dbg_printf("Remote Next Packet Pointer = %02x\n", BX_NE2K_THIS s.rempkt_ptr);
      break;
    case 0x0205:
      dbg_printf("Local Next Packet Pointer = %02x\n", BX_NE2K_THIS s.localpkt_ptr);
      break;
    case 0x0206:
    case 0x0207:  BX_DUPLICATE(0x0206);
      dbg_printf("Address Counter= %02x %02x\n",
         BX_HIGH_BYTE(BX_NE2K_THIS s.address_cnt),
         BX_LOW_BYTE(BX_NE2K_THIS s.address_cnt));
      break;
    case 0x0208:
    case 0x0209:  BX_DUPLICATE(0x0208);
    case 0x020A:  BX_DUPLICATE(0x0208);
    case 0x020B:  BX_DUPLICATE(0x0208);
      if (!brief) dbg_printf ("Reserved\n");
    case 0xffff:
      dbg_printf("IMR (Interrupt Mask Register):\n  ");
      dbg_printf("\n");
      break;
    default:
      dbg_printf("NE2K info: sorry, page %d register %d cannot be displayed.\n", page, reg);
  }
  if (!brief)
    dbg_printf("\n");
}
#endif

#endif /* if BX_SUPPORT_NE2K */
