/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2001-2025  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 "unmapped.h"

#include "bx_debug/debug.h"

#define LOG_THIS theUnmappedDevice->

bx_unmapped_c *theUnmappedDevice = NULL;

PLUGIN_ENTRY_FOR_MODULE(unmapped)
{
  if (mode == PLUGIN_INIT) {
    theUnmappedDevice = new bx_unmapped_c();
    BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theUnmappedDevice, BX_PLUGIN_UNMAPPED);
  } else if (mode == PLUGIN_FINI) {
    delete theUnmappedDevice;
  } else if (mode == PLUGIN_PROBE) {
    return (int)PLUGTYPE_OPTIONAL;
  }
  return(0); // Success
}

bx_unmapped_c::bx_unmapped_c(void)
{
  put("unmapped", "UNMAP");
}

bx_unmapped_c::~bx_unmapped_c(void)
{
  BX_DEBUG(("Exit"));
}

void bx_unmapped_c::init(void)
{
  DEV_register_default_ioread_handler(this, read_handler, "Unmapped", 7);
  DEV_register_default_iowrite_handler(this, write_handler, "Unmapped", 7);

  s.port80 = 0x00;
  s.port8e = 0x00;
  s.shutdown = 0;
  s.port_e9_hack = SIM->get_param_bool(BXPN_PORT_E9_HACK)->get();
  SIM->get_param_num(BXPN_PORT_E9_HACK)->set_handler(param_handler);
}

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

Bit32u bx_unmapped_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_UM_SMF
  UNUSED(io_len);

  Bit32u retval;

  // This function gets called for access to any IO ports which
  // are not mapped to any device handler.

  switch (address) {
    case 0x80:
      retval = BX_UM_THIS s.port80;
      break;
    case 0x8e:
      retval = BX_UM_THIS s.port8e;
      break;

    // Unused port on ISA - this can be used by the emulated code
    // to detect it is running inside Bochs and that the debugging
    // features are available (write 0xFF or something on unused
    // port 0x80, then read from 0xe9, if value is 0xe9, debug
    // output is available) (see write() for that) -- Andreas and Emmanuel
    case 0xe9:
      if (BX_UM_THIS s.port_e9_hack) {
         retval = 0xe9;
      }
      else {
         retval = 0xffffffff;
      }
      break;

    case 0x03df:
      retval = 0xffffffff;
      BX_DEBUG(("unsupported IO read from port %04x (CGA)", address));
      break;
    case 0x023a:
    case 0x02f8: /* UART */
    case 0x02f9: /* UART */
    case 0x02fb: /* UART */
    case 0x02fc: /* UART */
    case 0x02fd: /* UART */
    case 0x02ea:
    case 0x02eb:
    case 0x03e8:
    case 0x03e9:
    case 0x03ea:
    case 0x03eb:
    case 0x03ec:
    case 0x03ed:
    case 0x03f8: /* UART */
    case 0x03f9: /* UART */
    case 0x03fb: /* UART */
    case 0x03fc: /* UART */
    case 0x03fd: /* UART */
    case 0x17c6:
      retval = 0xffffffff;
      break;
    default:
      retval = 0xffffffff;
    }

  switch (io_len) {
    case 1:
      retval = (Bit8u)retval;
      BX_DEBUG(("8-bit read from %04x = %02x", address, retval));
      break;
    case 2:
      retval = (Bit16u)retval;
      BX_DEBUG(("16-bit read from %04x = %04x", address, retval));
      break;
    case 4:
      BX_DEBUG(("32-bit read from %04x = %08x", address, retval));
      break;
    default:
      BX_PANIC(("%d-bit read from %04x = %x", io_len * 8, address, retval));
  }

  return retval;
}

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

void bx_unmapped_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_UM_SMF
  bx_unmapped_c *class_ptr = (bx_unmapped_c *) this_ptr;
  class_ptr->write(address, value, io_len);
}

void bx_unmapped_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_UM_SMF
  UNUSED(io_len);

  // This function gets called for access to any IO ports which
  // are not mapped to any device handler. Writes to an unmapped
  // IO port are ignored.

  switch (address) {
    case 0x80: // diagnostic test port to display progress of POST
      //BX_DEBUG(("Diagnostic port 80h: write = %02xh", (unsigned) value));
      BX_UM_THIS s.port80 = value;
      break;

    case 0x8e: // ???
      BX_UM_THIS s.port8e = value;
      break;

    // This port doesn't exist on normal ISA architecture. However,
    // we define a convention here, to display on the console of the
    // system running Bochs, anything that is written to it. The
    // idea is to provide debug output very early when writing
    // BIOS or OS code for example, without having to bother with
    // properly setting up a serial port or anything.
    //
    // Idea by Andreas Beck (andreas.beck@ggi-project.org)

    case 0xe9:
      if (BX_UM_THIS s.port_e9_hack) {
        putchar(value);
        fflush(stdout);
      }
      break;

    case 0xed: // Dummy port used as I/O delay
      break;
    case 0xee: // ???
      break;

    case 0x2f2:
    case 0x2f3:
    case 0x2f4:
    case 0x2f5:
    case 0x2f6:
    case 0x2f7:
    case 0x3e8:
    case 0x3e9:
    case 0x3eb:
    case 0x3ec:
    case 0x3ed:
   // BX_DEBUG(("unsupported IO write to port %04x of %02x", address, value));
      break;

    case 0x8900: // Shutdown port, could be moved in a PM device
                 // or a host <-> guest communication device
      switch (value) {
        case 'S': if (BX_UM_THIS s.shutdown == 0) BX_UM_THIS s.shutdown = 1; break;
        case 'h': if (BX_UM_THIS s.shutdown == 1) BX_UM_THIS s.shutdown = 2; break;
        case 'u': if (BX_UM_THIS s.shutdown == 2) BX_UM_THIS s.shutdown = 3; break;
        case 't': if (BX_UM_THIS s.shutdown == 3) BX_UM_THIS s.shutdown = 4; break;
        case 'd': if (BX_UM_THIS s.shutdown == 4) BX_UM_THIS s.shutdown = 5; break;
        case 'o': if (BX_UM_THIS s.shutdown == 5) BX_UM_THIS s.shutdown = 6; break;
        case 'w': if (BX_UM_THIS s.shutdown == 6) BX_UM_THIS s.shutdown = 7; break;
        case 'n': if (BX_UM_THIS s.shutdown == 7) BX_UM_THIS s.shutdown = 8; break;
#if BX_DEBUGGER
        // Very handy for debugging:
        // output 'D' to port 8900, and bochs quits to debugger
        case 'D': bx_debug_break(); break;
#endif
        default : BX_UM_THIS s.shutdown = 0; break;
      }
      if (BX_UM_THIS s.shutdown == 8) {
        bx_user_quit = 1;
        BX_FATAL(("Shutdown port: shutdown requested"));
      }
      break;
    default:
      break;
  }

  switch (io_len) {
    case 1:
      BX_DEBUG(("8-bit write to %04x = %02x", address, value));
      break;
    case 2:
      BX_DEBUG(("16-bit write to %04x = %04x", address, value));
      break;
    case 4:
      BX_DEBUG(("32-bit write to %04x = %08x", address, value));
      break;
    default:
      BX_PANIC(("%d-bit write to %04x = %x", io_len * 8, address, value));
      break;
  }
}

Bit64s bx_unmapped_c::param_handler(bx_param_c *param, bool set, Bit64s val)
{
  if (set) {
    char pname[BX_PATHNAME_LEN];
    param->get_param_path(pname, BX_PATHNAME_LEN);
    if (set) {
      if (!strcmp(pname, BXPN_PORT_E9_HACK)) {
        BX_UM_THIS s.port_e9_hack = (val != 0);
      } else {
        BX_PANIC(("param_handler called with unexpected parameter '%s'", pname));
      }
    }
  }
  return val;
}
