/*

   Copyright (c) 2015-2017, John Forecast

   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
   JOHN FORECAST 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 John Forecast shall not
   be used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from John Forecast.

*/

/* cdc1700_dc.c: CDC1700 Buffered data channel support
 *               Simh devices: dca, dcb, dcc
 */

#include "cdc1700_defs.h"

extern char INTprefix[];

extern uint16 Areg, Preg, Qreg, IOAreg, IOQreg, M[];

extern t_bool IOFWinitialized;

extern DEVICE *IOdev[];
extern UNIT cpu_unit;

extern uint16 LoadFromMem(uint16);
extern t_bool IOStoreToMem(uint16, uint16, t_bool);

extern void rebuildPending(void);
extern void RaiseExternalInterrupt(DEVICE *);

extern IO_DEVICE *fw_findChanDevice(IO_DEVICE *, uint16);
extern enum IOstatus fw_doIO(DEVICE *, t_bool);
extern enum IOstatus fw_doBDCIO(IO_DEVICE *, uint16 *, t_bool, uint8);

extern uint16 LoadFromMem(uint16);
extern t_bool IOStoreToMem(uint16, uint16, t_bool);

t_stat set_intr(UNIT *uptr, int32 val, CONST char *, void *);
t_stat show_intr(FILE *, UNIT *, int32, CONST void *);
t_stat show_target(FILE *, UNIT *, int32, CONST void *);

t_stat dc_svc(UNIT *);
t_stat dc_reset(DEVICE *);

void DCstate(const char *, DEVICE *, IO_DEVICE *);
t_bool DCreject(IO_DEVICE *, t_bool, uint8);
enum IOstatus DCin(IO_DEVICE *, uint8);
enum IOstatus DCout(IO_DEVICE *, uint8);

t_stat dc_help(FILE *, DEVICE *, UNIT *, int32, const char *);

/*
        1706-A Buffered Data Channel

    Addresses (A maximum of 3 1706-A's may be attached to a 1700 series system)

                                        Computer Instruction
    Q Register                  Output From A           Input To A
    (Bits 11-15)

    #1     #2     #3
  00010  00111  01100           Direct Output           Direct Input
  00011  01000  01101           Function                Terminate Buffer
  00100  01001  01110           Buffered Output         1706-A Status
  00101  01010  01111           Buffered Input          1706-A Current Address

  Operations:

  Function

    15  14                                                   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   |                                                   |   |
     |   +---------------------------------------------------+   EOP Interrupt
     |                             |                             Request
     |                             Not defined
     Set/Clear condition bits 0 - 14

  Status Response:

  Status

    15                  10           7       5       3           0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X | X |   |   | X |   | X |   | X |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                             |   |       |       |       |   |   |
                             |   |       |       |       |   |   Ready
                             |   |       |       |       |   Busy
                             |   |       |       |       Interrupt
                             |   |       |       End of Operation
                             |   |       Program Protect Fault
                             |   Device Reject
                             Device Reply
 */

IO_DEVICE DCAdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_1_A,
                         DCreject, DCin, DCout, NULL, NULL,
                         DCstate, NULL, NULL, NULL, NULL, NULL,
                         0x7F, 4,
                         MASK_REGISTER0 | MASK_REGISTER1 |      \
                         MASK_REGISTER2 | MASK_REGISTER3,
                         MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL);

IO_DEVICE DCBdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_2_A,
                         DCreject, DCin, DCout, NULL, NULL,
                         DCstate, NULL, NULL, NULL, NULL, NULL,
                         0x7F, 4,
                         MASK_REGISTER0 | MASK_REGISTER1 |      \
                         MASK_REGISTER2 | MASK_REGISTER3,
                         MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL);

IO_DEVICE DCCdev = IODEV(NULL, "1706-A", DC, 0, 0xFF, IO_1706_3_A,
                         DCreject, DCin, DCout, NULL, NULL,
                         DCstate, NULL, NULL, NULL, NULL, NULL,
                         0x7F, 4,
                         MASK_REGISTER0 | MASK_REGISTER1 |      \
                         MASK_REGISTER2 | MASK_REGISTER3,
                         MASK_REGISTER2, 0, 0, DEVICE_DC, 0, NULL);

/*
 * Define usage for "private" IO_DEVICE data areas.
 */
#define iod_lastIO      iod_private
#define iod_target      iod_private2
#define iod_svcstate    iod_private3
#define iod_CWA         iod_private6
#define iod_LWA         iod_private7
#define iod_nextAddr    iod_private8
#define iod_reg         iod_private9

/*
 * Define current state of the 1706-A with respect to the Direct Storage
 * Access Bus.
 */
#define IO_BDC_IDLE     0x00
#define IO_BDC_STARTR   0x01            /* Start read sequence */
#define IO_BDC_STARTW   0x02            /* Start write sequence */
#define IO_BDC_READING  0x03            /* Read sequence in progress */
#define IO_BDC_WRITING  0x04            /* Write sequence in  progress */
#define IO_BDC_DONE     0x05            /* Transfer has completed */

/* Buffered Data Channel (DC) data structures

   dca_dev      DC device descriptor
   dcb_dev      DC device descriptor
   dcc_dev      DC device descriptor
   dca_unit     DC unit
   dcb_unit     DC unit
   dcc_unit     DC unit
   dc_reg       DC register list
   dc_mod       DC modifier list
*/
UNIT dca_unit[] = {
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) }
};
UNIT dcb_unit[] = {
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) }
};
UNIT dcc_unit[] = {
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) },
  {UDATA(&dc_svc, UNIT_DISABLE, 0) }
};

REG dca_reg[] = {
  { HRDATAD(STATUS, DCAdev.iod_readR[2], 16, "1706 Status") },
  { HRDATAD(CWA, DCAdev.iod_CWA, 16, "1706 Current Address") },
  { HRDATAD(NEXT, DCAdev.iod_nextAddr, 16, "Next transfer address") },
  { HRDATAD(LWA, DCAdev.iod_LWA, 16, "Last word address") },
  { HRDATAD(IENABLE, DCAdev.IENABLE, 16, "Interrupt enabled") },
  { NULL }
};

REG dcb_reg[] = {
  { HRDATAD(STATUS, DCBdev.iod_readR[2], 16, "1706 Status") },
  { HRDATAD(CWA, DCBdev.iod_CWA, 16, "1706 Current Address") },
  { HRDATAD(NEXT, DCBdev.iod_nextAddr, 16, "Next transfer address") },
  { HRDATAD(LWA, DCBdev.iod_LWA, 16, "Last word address") },
  { HRDATAD(IENABLE, DCBdev.IENABLE, 16, "Interrupt enabled") },
  { NULL }
};

REG dcc_reg[] = {
  { HRDATAD(STATUS, DCCdev.iod_readR[2], 16, "1706 Status") },
  { HRDATAD(CWA, DCCdev.iod_CWA, 16, "1706 Current Address") },
  { HRDATAD(NEXT, DCCdev.iod_nextAddr, 16, "Next transfer address") },
  { HRDATAD(LWA, DCCdev.iod_LWA, 16, "Last word address") },
  { HRDATAD(IENABLE, DCCdev.IENABLE, 16, "Interrupt enabled") },
  { NULL }
};

MTAB dc_mod[] = {
  { MTAB_XTD|MTAB_VDV, 0, "1706-A Buffered Data Channel" },
  { MTAB_XTD|MTAB_VDV, 0, "TARGET", NULL,
    NULL, &show_target, NULL, "Display devices attached to the data channel" },
  { MTAB_XTD|MTAB_VDV, 0, "INTERRUPT", "INTERRUPT=hexValue",
    &set_intr, &show_intr, NULL, "Display data channel interrupt" },
  { 0 }
};

DEBTAB dc_deb[] = {
  { "TRACE",       DBG_DTRACE,     "Trace device I/O requests" },
  { "STATE",       DBG_DSTATE,     "Display device state changes" },
  { "INTR",        DBG_DINTR,      "Display device interrupt requests" },
  { "LOCATION",    DBG_DLOC,       "Display address for I/O instructions" },
  { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DLOC },
  { NULL }
};

DEVICE dca_dev = {
  "DCA", dca_unit, dca_reg, dc_mod,
  0, 16, 16, 1, 16, 16,
  NULL, NULL, &dc_reset,
  NULL, NULL, NULL,
  &DCAdev,
  DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb,
  NULL, NULL, &dc_help, NULL, NULL, NULL
};

DEVICE dcb_dev = {
  "DCB", dcb_unit, dcb_reg, dc_mod,
  0, 16, 16, 1, 16, 16,
  NULL, NULL, &dc_reset,
  NULL, NULL, NULL,
  &DCBdev,
  DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb,
  NULL, NULL, &dc_help, NULL, NULL, NULL
};

DEVICE dcc_dev = {
  "DCC", dcc_unit, dcc_reg, dc_mod,
  0, 16, 16, 1, 16, 16,
  NULL, NULL, &dc_reset,
  NULL, NULL, NULL,
  &DCCdev,
  DEV_DEBUG | DEV_NOEQUIP | DEV_INDEV | DEV_OUTDEV, 0, dc_deb,
  NULL, NULL, &dc_help, NULL, NULL, NULL
};

static DEVICE *dc_devices[IO_1706_MAX] = {
  &dca_dev, &dcb_dev, &dcc_dev
};

/*
 * Dump the current state of the Buffered Data Channel.
 */
const char *DCstateStr[] = {
  "Idle", "StartR", "StartW", "Read", "Write", "Done"
};

void DCstate(const char *where, DEVICE *dev, IO_DEVICE *iod)
{
  fprintf(DBGOUT,
          "%s[%s %s: Sta: %04X, %s, ena: %04X, cur: %04X, next: %04X, last: %04X, reg: %d]\r\n",
          INTprefix, dev->name, where,
          DCSTATUS(iod), DCstateStr[iod->iod_svcstate], ENABLED(iod),
          iod->iod_CWA, iod->iod_nextAddr, iod->iod_LWA, iod->iod_reg);
}

/*
 * Display device description.
 */
static const char *description(DEVICE *dptr)
{
  return "1706-A";
}

/*
 * Unit service
 */
t_stat dc_svc(UNIT *uptr)
{
  DEVICE *dptr;
  enum IOstatus status;
  uint16 temp = 0;

  if ((dptr = find_dev_from_unit(uptr)) != NULL) {
    IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt;
    IO_DEVICE *target = (IO_DEVICE *)iod->iod_target;

    if ((dptr->dctrl & DBG_DSTATE) != 0)
      DCstate("dc_svc() entry", iod->iod_indev, iod);

    switch (iod->iod_svcstate) {
      case IO_BDC_IDLE:
        return SCPE_OK;

      case IO_BDC_STARTR:
      case IO_BDC_STARTW:
        if ((dptr->dctrl & DBG_DTRACE) != 0)
          fprintf(DBGOUT,
                  "%s%s - Start %s on %s, current: %04X, last: %04X\r\n",
                  INTprefix, dptr->name,
                  iod->iod_svcstate == IO_BDC_STARTR ? "input" : "output",
                  target == NULL ? "no device" : target->iod_indev->name,
                  iod->iod_CWA, iod->iod_LWA);

        iod->iod_svcstate =
          iod->iod_svcstate == IO_BDC_STARTR ? IO_BDC_READING : IO_BDC_WRITING;
        sim_activate(uptr, DC_IO_WAIT);

        if ((dptr->dctrl & DBG_DSTATE) != 0)
          DCstate("dc_svc() - started", iod->iod_indev, iod);

        return SCPE_OK;

      case IO_BDC_READING:
        if (target != NULL) {
          if ((target->STATUS & IO_ST_EOP) != 0)
            goto ioDone;

          if (iod->iod_CWA == iod->iod_LWA) {
            /*
             * Transfer complete - complete status change and, optionally,
             * generate interrupt.
             */
            iod->iod_svcstate = IO_BDC_DONE;
            sim_activate(uptr, DC_EOP_WAIT);
            
            if ((dptr->dctrl & DBG_DSTATE) != 0)
              DCstate("dc_svc() - read complete", iod->iod_indev, iod);

            return SCPE_OK;
          }

          DCSTATUS(iod) &= ~(IO_1706_REPLY | IO_1706_REJECT);
          iod->iod_nextAddr = iod->iod_CWA + 1;

          status = fw_doBDCIO(target, &temp, FALSE, iod->iod_reg);

          switch (status) {
            case IO_REPLY:
              DCSTATUS(iod) |= IO_1706_REPLY;
              if (!IOStoreToMem(iod->iod_CWA, temp, TRUE)) {
                DCSTATUS(iod) |= IO_1706_PROT;
                /*** TODO: Signal protect fault ***/
              }
              iod->iod_CWA++;

              if ((dptr->dctrl & DBG_DTRACE) != 0)
                fprintf(DBGOUT,
                        "%s%s - Read %04X\r\n",
                        INTprefix, dptr->name, temp);
              break;

            case IO_REJECT:
            case IO_INTERNALREJECT:
              DCSTATUS(iod) |= IO_1706_REJECT;
              break;
          }
        } else DCSTATUS(iod) |= IO_1706_REJECT;
        sim_activate(uptr, DC_IO_WAIT);

        if ((dptr->dctrl & DBG_DSTATE) != 0)
          DCstate("dc_svc() - reading", iod->iod_indev, iod);

        return SCPE_OK;

      case IO_BDC_WRITING:
        if (target != NULL) {
          if ((target->STATUS & IO_ST_EOP) != 0)
            goto ioDone;

          if (iod->iod_CWA == iod->iod_LWA) {
            /*
             * Transfer complete - complete status change and, optionally,
             * generate interrupt.
             */
            iod->iod_svcstate = IO_BDC_DONE;
            sim_activate(uptr, DC_EOP_WAIT);
            
            if ((dptr->dctrl & DBG_DSTATE) != 0)
              DCstate("dc_svc() - write complete", iod->iod_indev, iod);
            
            return SCPE_OK;
          }

          DCSTATUS(iod) &= ~(IO_1706_REPLY | IO_1706_REJECT);
          iod->iod_nextAddr = iod->iod_CWA + 1;

          temp = LoadFromMem(iod->iod_CWA);
          status = fw_doBDCIO(target, &temp, TRUE, iod->iod_reg);

          switch (status) {
            case IO_REPLY:
              DCSTATUS(iod) |= IO_1706_REPLY;
              iod->iod_CWA++;
              break;

            case IO_REJECT:
            case IO_INTERNALREJECT:
              DCSTATUS(iod) |= IO_1706_REJECT;
              break;
          }
        } else DCSTATUS(iod) |= IO_1706_REJECT;
        sim_activate(uptr, DC_IO_WAIT);

        if ((dptr->dctrl & DBG_DSTATE) != 0)
          DCstate("dc_svc() - writing", iod->iod_indev, iod);

        return SCPE_OK;

      case IO_BDC_DONE:
        /*
         * The transfer has completed as far as the 1706-A is concerned.
         */
    ioDone:
        iod->iod_svcstate = IO_BDC_IDLE;

        DCSTATUS(iod) |= IO_ST_EOP;
        DCSTATUS(iod) &= ~IO_ST_BUSY;

        if (ISENABLED(iod, IO_DIR_EOP) && (iod->iod_equip != 0)) {
          DEVICE *dptr = iod->iod_indev;
          
          if ((dptr->dctrl & DBG_DINTR) != 0)
            fprintf(DBGOUT,
                    "%s%s - Generate EOP interrupt\r\n",
                    INTprefix, dptr->name);
          DCSTATUS(iod) |= IO_ST_INT;
          RaiseExternalInterrupt(dptr);
        }

        if ((dptr->dctrl & DBG_DSTATE) != 0)
          DCstate("dc_svc() - EOP set", iod->iod_indev, iod);

        return SCPE_OK;
    }
  }
  return SCPE_NXDEV;
}

/*
 * Reset routine
 */
t_stat dc_reset(DEVICE *dptr)
{
  IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt;

  DEVRESET(iod);

  DCSTATUS(iod) = IO_ST_READY;

  return SCPE_OK;
}

/*
 * Set the interrupt level for a buffered data channel.
 */
t_stat set_intr(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
  IO_DEVICE *iod = (IO_DEVICE *)uptr->up7;
  t_value v;
  t_stat r;

  if (cptr == NULL)
    return SCPE_ARG;

  v = get_uint(cptr, DEV_RDX, 15, &r);
  if (r != SCPE_OK)
    return r;
  if (v == 0)
    return SCPE_ARG;

  iod->iod_equip = v;
  iod->iod_interrupt = 1 << v;
  return SCPE_OK;
}

/*
 * Display the current interrupt level.
 */
t_stat show_intr(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
  IO_DEVICE *iod = (IO_DEVICE *)uptr->up7;

  if (iod->iod_equip != 0) {
    fprintf(st, "Interrupt: ");
    fprint_val(st, (t_value)iod->iod_equip, DEV_RDX, 8, PV_LEFT);
  } else fprintf(st, "Interrupt: None");
  return SCPE_OK;
}

/*
 * Display buffered data channel target device and equipment address
 */
t_stat show_target(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
  IO_DEVICE *iod;

  if (uptr == NULL)
    return SCPE_IERR;

  if ((iod = (IO_DEVICE *)uptr->up8) != NULL) {
    DEVICE *dptr = iod->iod_indev;

    fprintf(st, "\n\tTarget: %s (%s), Equip: %d",
            sim_dname(dptr), iod->iod_model, iod->iod_equip);
  }
  return SCPE_OK;
}

/*
 * Check if I/O should be rejected. I/O allowed if:
 *
 * Reg.                 Write (OUT)                     Read (INP)
 *
 *  00          Not busy                        Not busy
 *  01          Not busy                        Always allowed
 *  02          Not busy                        Always allowed
 *  03          Not busy                        Always allowed
 */
t_bool DCreject(IO_DEVICE *iod, t_bool output, uint8 reg)
{
  if (output || (reg == 0))
    return (DCSTATUS(iod) & IO_ST_BUSY) != 0;

  return FALSE;
}

/*
 * Start a buffered data channel transfer. Note that target may be NULL if
 * an attempt is made to access a device which is not connected to the
 * buffered data channel. We need to delay starting the transaction so that
 * there is sufficient time to grab the current bufferered data channel
 * status and terminate the transfer before starting the actual transfer.
 * The diagnostics check for this particular case.
 */
enum IOstatus DCxfer(IO_DEVICE *iod, IO_DEVICE *target, t_bool output)
{
  DEVICE *dptr = (DEVICE *)iod->iod_indev;

  iod->iod_LWA = LoadFromMem(IOAreg);
  iod->iod_CWA = iod->iod_nextAddr = ++IOAreg;
  iod->iod_target = target;
  if (target != NULL)
    iod->iod_reg = IOQreg & target->iod_rmask;

  DCSTATUS(iod) &= ~IO_ST_EOP;
  DCSTATUS(iod) |= IO_ST_BUSY;

  iod->iod_svcstate = output ? IO_BDC_STARTW : IO_BDC_STARTR;
  sim_activate(&dptr->units[0], DC_START_WAIT);

  if ((dptr->dctrl & DBG_DTRACE) != 0)
    fprintf(DBGOUT,
            "%s%s - starting %s transfer, cur: %04X, last: %04X\r\n",
            INTprefix, dptr->name, output ? "output" : "input",
            iod->iod_CWA, iod->iod_LWA);

  return IO_REPLY;
}

/*
 * Perform a buffered data channel input operation
 */
enum IOstatus DCin(IO_DEVICE *iod, uint8 reg)
{
  IO_DEVICE *target;
  enum IOstatus status;

  /*
   * If the "Continue" bit is set in Q, use the last I/O address and treat the
   * request as a direct input/output operation.
   */
  if ((IOQreg & IO_CONTINUE) != 0) {
    IOQreg = iod->iod_lastIO;
    reg = 0;
  } else iod->iod_lastIO = IOQreg;

  /*
   * The framework filters out INP requests for the status register.
   */
  switch (reg) {
    /*
     * Perform a direct input request from the target device.
     */
    case 0x00:
      /*
       * Find the target device to be used.
       */
      if ((target = fw_findChanDevice(iod, IOQreg)) == NULL)
        return IO_REJECT;

      if ((target->iod_indev->dctrl & DBG_DSTATE) != 0)
        if (target->iod_state != NULL)
          (*target->iod_state)("before direct in", target->iod_indev, target);

      status = fw_doIO(target->iod_indev, FALSE);

      if ((target->iod_indev->dctrl & DBG_DSTATE) != 0)
        if (target->iod_state != NULL)
          (*target->iod_state)("after direct in", target->iod_indev, target);

      return status;

    /*
     * Terminate buffer, 1706 Current Address.
     */
    case 0x01:
      iod->iod_svcstate = IO_BDC_IDLE;
      DCSTATUS(iod) &= ~IO_ST_BUSY;
      /* FALLTHROUGH */

    /*
     * 1706 Current Address. May be the next address depending on where we
     * are in the actual transfer sequence.
     */
    case 0x03:
      Areg = iod->iod_nextAddr;
      return IO_REPLY;
  }
  return IO_REJECT;
}

/*
 * Perform a buffered data channel output operation
 */
enum IOstatus DCout(IO_DEVICE *iod, uint8 reg)
{
  IO_DEVICE *target;
  enum IOstatus status;

  /*
   * If the "Continue" bit is set in Q, use the last I/O address and treat the
   * request as a direct input/output operation.
   */
  if ((IOQreg & IO_CONTINUE) != 0) {
    IOQreg = iod->iod_lastIO;
    reg = 0;
  } else iod->iod_lastIO = IOQreg;

  /*
   * Find the target device to be used. If the target device is not connected
   * to the buffered data channel, the REJECT will eventually be processed
   * in dc_svc().
   */
  target = fw_findChanDevice(iod, IOQreg);

  if ((target == NULL) && (reg == 0x00))
    return IO_REJECT;

  switch (reg) {
    /*
     * Perform a direct output request to the target device.
     */
    case 0x00:
      if ((target->iod_indev->dctrl & DBG_DSTATE) != 0)
        if (target->iod_state != NULL)
          (*target->iod_state)("before direct out", target->iod_indev, target);

      status = fw_doIO(target->iod_indev, TRUE);

      if ((target->iod_indev->dctrl & DBG_DSTATE) != 0)
        if (target->iod_state != NULL)
          (*target->iod_state)("after direct out", target->iod_indev, target);

      return status;

    /*
     * Command function to the 1706-A.
     */
    case 0x01:
      if ((IOAreg & IO_1706_EOP) != 0) {
        iod->OLDIENABLE = iod->IENABLE;
        if ((IOAreg & IO_1706_SET) != 0)
          iod->IENABLE |= IO_DIR_EOP;
        else iod->IENABLE &= ~IO_DIR_EOP;

        DCSTATUS(iod) &= ~(IO_ST_INT | IO_ST_EOP);
        rebuildPending();
      }
      return IO_REPLY;

    /*
     * Initiate buffered output on the 1706-A.
     */
    case 0x02:
      return DCxfer(iod, target, TRUE);

    /*
     * Initiate buffered input on the 1706-A.
     */
    case 0x03:
      return DCxfer(iod, target, FALSE);
  }
  return IO_REJECT;
}

/*
 * Build the buffered data channel tables.
 */
void buildDCtables(void)
{
  int i;
  uint8 chan;
  DEVICE *dptr;
  UNIT *uptr;

  dca_dev.numunits = 0;
  dcb_dev.numunits = 0;
  dcc_dev.numunits = 0;

  dca_dev.units[0].up7 = &DCAdev;
  dcb_dev.units[0].up7 = &DCBdev;
  dcc_dev.units[0].up7 = &DCCdev;

  for (i = 0; i < 16; i++) {
    if ((dptr = IOdev[i]) != NULL) {
      IO_DEVICE *iod = (IO_DEVICE *)dptr->ctxt;

      if (((chan = iod->iod_dc) != 0) &&
          ((iod->iod_flags & AQ_ONLY) == 0)) {
        dptr = dc_devices[IDX_FROM_CHAN(chan)];
        uptr = &dptr->units[dptr->numunits];
        if (dptr->numunits < IO_1706_DEVS) {
          uptr->up8 = iod;
          dptr->numunits++;
        }
      }
    }
  }
}

/*
 * Create bit map of interrupts asserted by the Buffered Data Channels.
 */
uint16 dcINTR(void)
{
  uint16 result = 0;

  if ((DCSTATUS(&DCAdev) & IO_ST_INT) != 0)
    result |= DCAdev.iod_interrupt;
  if ((DCSTATUS(&DCBdev) & IO_ST_INT) != 0)
    result |= DCBdev.iod_interrupt;
  if ((DCSTATUS(&DCCdev) & IO_ST_INT) != 0)
    result |= DCCdev.iod_interrupt;

  return result;
}

t_stat dc_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
  const char helpString[] =
    " The %D device is a 1706-A buffered data channel.\n"
    "1 Hardware Description\n"
    " The 1706-A consists of a controller which can control up to 8 other\n"
    " controllers to provide them with direct memory access. Up to 3\n"
    " 1706-A's may be connected to the CPU. All 3 buffered data channels are\n"
    " available in the simulator but only channel 1 (DCA) is connected to a\n"
    " peripheral (the magtape controller) and only if that controller is\n"
    " configured as a 1732-A. Unlike actual hardware, the simulator allows\n"
    " access to the magtape controller either through the A/Q channel or\n"
    " through a 1706-A.\n\n"
    " By default, the simulator does not assign an interrupt to a 1706-A. An\n"
    " interrupt may be assigned with the command:\n\n"
    "+sim> SET %D INTERRUPT=hexValue\n"
    "2 Equipment Address\n"
    " Unlike most peripherals, buffered data channels use private addresses\n"
    " outside the normal 1 - 15 address range.\n"
    "2 $Registers\n"
    "1 Configuration\n"
    " A %D device is configured with various simh SET commands\n"
    "2 $Set commands\n";

  return scp_help(st, dptr, uptr, flag, helpString, cptr);
}
