/* h316_imp.c- BBN ARPAnet IMP/TIP Specific Hardware
   Based on the SIMH simulator package written by Robert M Supnik.

   Copyright (c) 2013 Robert Armstrong, bob@jfcl.com.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT ARMSTRONG BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Robert Armstrong shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert Armstrong.

   tks          task switch device
   mlc          multiline controller (aka TIP)

   21-May-13    RLA     New file

   OVERVIEW

   This module implements the IMP pseudo device - this hack takes care of two
   custom devices in the IMP hardware - device 041, which implements task 
   switching and the RDIMPN instruction, and device 42, which implements the
   AMIMLC ("am I a multiline controller") instruction. This module also contains
   a few miscellaneous routines which are used by the IMP support in general.  

   IMP state is maintained in a set of state variables:

        MLC             always zero (TIP flag)
        IEN             task interrupt enabled
        IRQ             task interrupt pending

   TODO
*/
#ifdef VM_IMPTIP
#include "h316_defs.h"                  // H316 emulator definitions
#include "h316_imp.h"                   // ARPAnet IMP/TIP definitions

// Locals ...
uint16 imp_station  = IMP_STATION;      // IMP number (or address)
uint16 imp_ismlc    = 0;                // 1 for MLC (not yet implemented!)

// Externals from other parts of simh ...
extern uint16 dev_ext_int, dev_ext_enb; // current IRQ and IEN bit vectors
extern int32 PC;                        // current PC (for debug messages)
extern int32 stop_inst;                 // needed by IOBADFNC()

// Forward declarations ...
int32  imp_io      (int32 inst, int32 fnc, int32 dat, int32 dev);
t_stat imp_service (UNIT *uptr);
t_stat imp_reset   (DEVICE *dptr);
t_stat imp_show_station (FILE *st, UNIT *uptr, int32 val, void *dp);
t_stat io_show_int (FILE *st, UNIT *uptr, int32 val, void *dp);
t_stat imp_set_station (UNIT *uptr, int32 val, char *cptr, void *dp);
t_stat io_set_int (UNIT *uptr, int32 val, char *cptr, void *dp);



////////////////////////////////////////////////////////////////////////////////
//////////////////////   D A T A    S T R U C T U R E S   //////////////////////
////////////////////////////////////////////////////////////////////////////////

// IMP device information block ...
DIB imp_dib = { IMP, 2, IOBUS, IOBUS, INT_V_TASK, INT_V_NONE, &imp_io, 0 };

// IMP unit data (we have only one!) ...
UNIT imp_unit = { UDATA (&imp_service, 0, 0) };

// IMP device registers (for "EXAMINE IMP STATE") ...
REG imp_reg[] = {
  { FLDATA (MLC, imp_ismlc, 0), REG_RO },
  { FLDATA (IEN, dev_ext_enb, INT_V_TASK-INT_V_EXTD) },
  { FLDATA (IRQ, dev_ext_int, INT_V_TASK-INT_V_EXTD) },
  { NULL }
};

// IMP device modifiers (for "SET/SHOW IMP xxx") ...
MTAB imp_mod[] = {
  { MTAB_XTD|MTAB_VDV, 0, "NUM", "NUM", &imp_set_station, &imp_show_station, NULL },
  { 0 }
};

// IMP debugging flags (for "SET IMP DEBUG=xxx") ...
DEBTAB imp_debug[] = {
  {"WARN",  IMP_DBG_WARN},
  {"IO",    IMP_DBG_IOT},
  {0}
};

// And finally tie it all together ...
DEVICE imp_dev = {
  "IMP", &imp_unit, imp_reg, imp_mod,
  1, 0, 0, 0, 0, 0,
  NULL, NULL, &imp_reset, NULL, NULL, NULL,
  &imp_dib, DEV_DIS|DEV_DISABLE|DEV_DEBUG, 0, imp_debug, NULL, NULL
};



////////////////////////////////////////////////////////////////////////////////
//////////   I M P   I / O   A N D   S E R V I C E   R O U T I N E S  //////////
////////////////////////////////////////////////////////////////////////////////

// Set and clear the TASK IRQ and IEN ...
#define SET_TASK_IRQ()  SET_EXT_INT((1u << (imp_dib.inum - INT_V_EXTD)))
#define CLR_TASK_IRQ()  CLR_EXT_INT((1u << (imp_dib.inum - INT_V_EXTD)))
#define CLR_TASK_IEN()  CLR_EXT_ENB((1u << (imp_dib.inum - INT_V_EXTD)))

// IMP I/O routine ...
int32 imp_io (int32 inst, int32 fnc, int32 dat, int32 dev)
{
  if (dev == IMP) {
    if ((inst == ioOCP) && (fnc == 000)) {
      // TASK - just set the task interrupt request bit ...
      sim_debug(IMP_DBG_IOT, &imp_dev, "request task interrupt (PC=%06o)\n", PC-1);
      SET_TASK_IRQ();  return dat;
    } else if ((inst == ioINA) && ((fnc == 010) || (fnc == 000))) {
      // RDIMPN - return the IMP address and always skip ...
      sim_debug(IMP_DBG_IOT, &imp_dev, "read address (PC=%06o)\n", PC-1);
      return IOSKIP(imp_station);
    }
  } else if (dev == IMP+1) {
    if ((inst == ioSKS) && (fnc == 000)) {
      // AMIMLC - skip if this machine is an MLC ...
      sim_debug(IMP_DBG_IOT, &imp_dev, "skip on MLC (PC=%06o %s)\n", PC-1, imp_ismlc ? "SKIP" : "NOSKIP");
      if (imp_ismlc != 0) return IOSKIP(dat);  else return dat;
    }
  }

  // Anything else is an error...
  sim_debug(IMP_DBG_WARN, &imp_dev, "UNIMPLEMENTED I/O (PC=%06o, instruction=%o, function=%02o)\n", PC-1, inst, fnc);
  return IOBADFNC(dat);
}

// Unit service ...
t_stat imp_service (UNIT *uptr)
{
  return SCPE_OK;
}



////////////////////////////////////////////////////////////////////////////////
///////////////   D E V I C E   A C T I O N   C O M M A N D S   ////////////////
////////////////////////////////////////////////////////////////////////////////

// Reset routine ...
t_stat imp_reset (DEVICE *dptr)
{
  // The simh RESET command clears both the interrupt request and enable...
  CLR_TASK_IRQ();  CLR_TASK_IEN();
  return SCPE_OK;
}



////////////////////////////////////////////////////////////////////////////////
/////////   D E V I C E   S E T   A N D   S H O W   C O M M A N D S   //////////
////////////////////////////////////////////////////////////////////////////////

// Show the station number ...
t_stat imp_show_station (FILE *st, UNIT *uptr, int32 val, void *desc)
{
  fprintf(st,"station=%d", imp_station);
  return SCPE_OK;
}

// Set the station number ...
t_stat imp_set_station (UNIT *uptr, int32 val, char *cptr, void *dp)
{
  uint32 newnum;  t_stat sts;
  if (cptr == NULL) return SCPE_ARG;
  newnum = get_uint (cptr, 10, 9999, &sts);
  if (newnum == 0) return SCPE_ARG;
  imp_station = newnum;
  return SCPE_OK;
}

#endif // #ifdef VM_IMPTIP from the very top
