/*

   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_drm.c: drum memory controller
 *                Simh device: drm
 */

/*
 * Notes:
 *
 * 1. The 1752 Drum Memory Subsystem consists of a 3600 RPM drum with 32
 *    sectors (96 words each) per track. There can be 64 - 1024 tracks
 *    depending on the model ordered.
 *
 *    There is 1 readable register which needs to be handled specially - the
 *    Sector Address Status. This register consists of 3 fields:
 *
 *      - Current sector address as read from the drum (0 - 31)
 *      - Current track address from last I/O request (zero on startup)
 *      - Core address compare - set if transfer is to last address of buffer
 *
 *    The SMM17 diagnostic for the 1752 uses this register to verify that
 *    the hardware is operational before allowing the diagnostic to run. The
 *    register fields will be implemented as follows:
 *
 *    1. Current sector address
 *
 *       Rather than use a repeating service routine, we will timestamp
 *       (using the instruction count) when the Sector Address Status register
 *       was last referenced or used as part of an I/O operation. When the
 *       register is next referenced, we will compute the number of sectors
 *       which have passed under the head (521 uSec/sector so 350 instructions
 *       assuming 1.5 microsecond/instruction) and update the sector address
 *       field appropriately. If an I/O is active, the sector address will
 *       reflect that used by the current I/O. This may result in a sudden
 *       change in value.
 *
 *    2. Current track address
 *
 *       This will be the last track address referenced by an I/O request.
 * 
 *    3. Core address compare
 *
 *       This bit is set when the current/next DMA request is to the last
 *       address of the buffer. Rather than simulate DMA word at a time,
 *       we simulate sector transfer at a time. We will set this bit if the
 *       last address of the buffer is somewhere within the current sector
 *       and also set the Core Address Status to be the last address of
 *       the buffer.
 *
 * 2. This is the first, and only, device driver which requires dynamic
 *    processing of the Director Status Register. The Sector Compare is only
 *    set when the requested sector is under the read head. The I/O framework
 *    did not require any changes to allow this to work.
 */

#include "cdc1700_defs.h"

#define SASTATUS        iod_readR[2]
#define CASTATUS        iod_readR[3]
#define DATASTATUS      iod_readR[4]

extern char INTprefix[];

extern void RaiseExternalInterrupt(DEVICE *);

extern t_bool doDirectorFunc(DEVICE *, t_bool);
extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8);
extern void fw_IOunderwayEOP2(IO_DEVICE *, uint16);
extern void fw_IOcompleteEOP2(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *);
extern void fw_IOalarm(t_bool, DEVICE *, IO_DEVICE *, const char *);
extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *);

extern void rebuildPending(void);

extern t_stat show_addr(FILE *, UNIT *, int32, CONST void *);

extern t_stat set_protected(UNIT *, int32, CONST char *, void *);
extern t_stat clear_protected(UNIT *, int32, CONST char *, void *);

extern t_stat set_equipment(UNIT *, int32, CONST char *, void *);

extern t_stat set_stoponrej(UNIT *, int32, CONST char *, void *);
extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *);

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

extern uint16 M[], Areg, IOAreg;
extern t_uint64 Instructions;

extern t_bool IOFWinitialized;

extern UNIT cpu_unit;

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

/* Constants */

#define DRM_NUMWD       (96)            /* words/sector */
#define DRM_NUMBY       (DRM_NUMWD * sizeof(uint16))
#define DRM_NUMSC       (32)            /* sectors/track */
#define DRM_SIZE        (512 * DRM_NUMSC * DRM_NUMBY)
#define DRM_MINTRACKS   (64)            /* Min # of tracks supported */
#define DRM_MAXTRACKS   (1024)          /* Max # of tracks supported */

#define DRM_AUTOLOAD    (16)            /* Sectors to autoload */

/*
 * Drum address fields
 */
#define DRM_TRK_MASK    0x7FE0          /* Track address */
#define DRM_TRK_SHIFT   5               /*    and shift */
#define DRM_SEC_MASK    0x001F          /* Sector mask */

#define DRM_COMPARE     0x8000          /* Core address compare */

enum drmio_status {
  DRMIO_MORE,                           /* More I/O pending */
  DRMIO_DONE,                           /* I/O processing complete */
  DRMIO_PROTECT,                        /* Protect fault */
  DRMIO_ADDRERR                         /* Addressing error */
};

t_stat drm_svc(UNIT *);
t_stat drm_reset(DEVICE *);
t_stat drm_attach(UNIT *, CONST char *);
t_stat drm_detach(UNIT *);

t_stat drm_set_size(UNIT *, int32, CONST char *, void *);

void DRMstate(const char *, DEVICE *, IO_DEVICE *);
t_bool DRMreject(IO_DEVICE *, t_bool, uint8);
enum IOstatus DRMin(IO_DEVICE *, uint8);
enum IOstatus DRMout(IO_DEVICE *, uint8);
void DRMclear(DEVICE *);
uint8 DRMdecode(IO_DEVICE *, t_bool, uint8);

/*
        1752-A/B/C/D Drum memory controller

   Addresses
                                Computer Instruction
   Q Register         Output From A        Input to A
  (Bits 03-00)

      0000              Initiate Write Op    Illegal
      0001              Director Function    Director Status
      0010              Illegal              Sector Address Status
      0011              Director Function    Core Address Status
      0100              Initiate Read Op     Data Status
      0101              Director Function    Illegal
      0110              Illegal              Illegal
      0111              Director Function    Illegal
      1000              Load ISA             Illegal
      1001              Director Function    Illegal
      1010              Illegal              Illegal
      1011              Director Function    Illegal
      1100              Load Initial Addr    Illegal
      1101              Director Function    Illegal
      1110              Load Final Addr      Illegal
      1111              Director Function    Illegal

  Operations:

  Initiate Drum Write Operation

    15  14                                                       0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

  Director Function

    15                                           4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X | X | X | X | X | X | X |   |   | X |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                                                 |   |       |   |
                                                 |   |       |   Clr Controller
                                                 |   |       Clr Interrupts
                                                 |   EOP Interrupt Req.
                                                 Interrupt on Alarm

  Initiate Drum Read Operation

    15  14                                                       0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

  Load Initial Sector Address

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | 0 |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
         |                                   |   |               |
         +-----------------------------------+   +---------------+
             Desired Initial Track Address        Desired Initial
                                                  Sector Addr - 1

  Load Initial Core Address, Load Final Core Address

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                                                           |
     +-----------------------------------------------------------+
                             Core Address

  Status Response:

  Director Status

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   Ready
     |   |   |   |   |   |   |   |   |   |   |   |   |   |   Busy
     |   |   |   |   |   |   |   |   |   |   |   |   |   Interrupt
     |   |   |   |   |   |   |   |   |   |   |   |   Data
     |   |   |   |   |   |   |   |   |   |   |   End of Operation
     |   |   |   |   |   |   |   |   |   |   Alarm
     |   |   |   |   |   |   |   |   |   Lost Data
     |   |   |   |   |   |   |   |   Protected
     |   |   |   |   |   |   |   Checkword Error
     |   |   |   |   |   |   Protect Fault
     |   |   |   |   |   Guarded Address Enabled
     |   |   |   |   Timing Track Error
     |   |   |   Power Failure
     |   |   Sector Compare
     |   Guarded Address Error
     Sector Overrange Error

  Sector Address Status

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |   |                                   |   |               |
     |   +-----------------------------------+   +---------------+
     |      Track Address Register Contents       Sector Address
     |                                            Register Contents
     Core Address Compare

  Core Address Status

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                                                           |
     +-----------------------------------------------------------+
                      Core Address Register Contents

  Data Status

    15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
     |                                                           |
     +-----------------------------------------------------------+
                       Data Register Zero Contents

*/

IO_DEVICE DRMdev = IODEV(NULL, "1752", 1752, 2, 0xFF, 0,
                         DRMreject, DRMin, DRMout, NULL, NULL,
                         DRMstate, NULL, NULL, DRMclear, DRMdecode, NULL,
                         0x7F, 16,
                         MASK_REGISTER0 | MASK_REGISTER1 | MASK_REGISTER2 | \
                         MASK_REGISTER3 | MASK_REGISTER4 | MASK_REGISTER5 | \
                         MASK_REGISTER6 | MASK_REGISTER7 | MASK_REGISTER8 | \
                         MASK_REGISTER9 | MASK_REGISTER10 | MASK_REGISTER11 | \
                         MASK_REGISTER12 | MASK_REGISTER13 | \
                         MASK_REGISTER14 | MASK_REGISTER15,
                         MASK_REGISTER3 | MASK_REGISTER4,
                         MASK_REGISTER0 | MASK_REGISTER5 | MASK_REGISTER6 | \
                         MASK_REGISTER7 | MASK_REGISTER8 | MASK_REGISTER9 | \
                         MASK_REGISTER10 | MASK_REGISTER11 | \
                         MASK_REGISTER12 | MASK_REGISTER13 | \
                         MASK_REGISTER14 | MASK_REGISTER15,
                         MASK_REGISTER2 | MASK_REGISTER6 | MASK_REGISTER10,
                         0, 0, NULL);

/*
 * Define usage for "private" IO_DEVICE data areas.
 */
#define iod_tracks      iod_private     /* # of tracks on device */
#define iod_compare     iod_private4    /* TRUE if DMA of last buffer word */
#define iod_isa         iod_private6    /* Initial sector address */
#define iod_ica         iod_private7    /* Initial core address */
#define iod_fca         iod_private8    /* Final core address */
#define iod_state       iod_private9    /* Current controller state */
#define iod_ca          iod_private11   /* Current DMA address */
#define iod_trk         iod_private12   /* Current track # */
#define iod_sec         iod_private13   /* Current sector # */

#define DRM_IDLE        0x00            /* Idle */
#define DRM_WRITE       0x01            /* Write data */
#define DRM_READ        0x02            /* Read data */

/* DRM data structures

   drm_dev      DRM device descriptor
   drm_unit     DRM unit descriptor
   drm_reg      DRM register list
   drm_mod      DRM modifier list
*/

UNIT drm_unit = {
  UDATA(&drm_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DRM_SIZE)
};

REG drm_reg[] = {
  { HRDATAD(FUNCTION, DRMdev.FUNCTION, 16, "Last director function issued") },
  { HRDATAD(STATUS, DRMdev.STATUS, 16, "Director status register") },
  { HRDATAD(IENABLE, DRMdev.IENABLE, 16, "Interrupts enabled") },
  /*** more ***/
  { NULL }
};

MTAB drm_mod[] = {
  { MTAB_XTD | MTAB_VDV, 0, "1752 Drum Memory Controller" },
  { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT=hexAddress",
    &set_equipment, &show_addr, NULL, "Set/Display equipment address" },
  { MTAB_XTD|MTAB_VDV, 0, NULL, "STOPONREJECT",
    &set_stoponrej, NULL, NULL, "Stop simulation if I/O is rejected" },
  { MTAB_XTD|MTAB_VDV, 0, NULL, "NOSTOPONREJECT",
    &clr_stoponrej, NULL, NULL, "Don't stop simulation if I/O is rejected" },
  { MTAB_XTD|MTAB_VDV, 0, NULL, "PROTECT",
    &set_protected, NULL, NULL, "Device is protected (unimplemented)" },
  { MTAB_XTD|MTAB_VDV, 0, NULL, "NOPROTECT",
    &clear_protected, NULL, NULL, "Device is unprotected (unimplemented)" },
  { UNIT_DRMSIZE, 64, NULL, "64",
    &drm_set_size, NULL, NULL, "Set drum storage to 64 tracks" },
  { UNIT_DRMSIZE, 128, NULL, "128",
    &drm_set_size, NULL, NULL, "Set drum storage to 128 tracks" },
  { UNIT_DRMSIZE, 192, NULL, "192",
    &drm_set_size, NULL, NULL, "Set drum storage to 192 tracks" },
  { UNIT_DRMSIZE, 256, NULL, "256",
    &drm_set_size, NULL, NULL, "Set drum storage to 256 tracks" },
  { UNIT_DRMSIZE, 320, NULL, "320",
    &drm_set_size, NULL, NULL, "Set drum storage to 320 tracks" },
  { UNIT_DRMSIZE, 384, NULL, "384",
    &drm_set_size, NULL, NULL, "Set drum storage to 384 tracks" },
  { UNIT_DRMSIZE, 448, NULL, "448",
    &drm_set_size, NULL, NULL, "Set drum storage to 448 tracks" },
  { UNIT_DRMSIZE, 512, NULL, "512",
    &drm_set_size, NULL, NULL, "Set drum storage to 512 tracks" },
  { UNIT_DRMSIZE, 576, NULL, "576",
    &drm_set_size, NULL, NULL, "Set drum storage to 576 tracks" },
  { UNIT_DRMSIZE, 640, NULL, "640",
    &drm_set_size, NULL, NULL, "Set drum storage to 640 tracks" },
  { UNIT_DRMSIZE, 704, NULL, "704",
    &drm_set_size, NULL, NULL, "Set drum storage to 704 tracks" },
  { UNIT_DRMSIZE, 768, NULL, "768",
    &drm_set_size, NULL, NULL, "Set drum storage to 768 tracks" },
  { UNIT_DRMSIZE, 832, NULL, "832",
    &drm_set_size, NULL, NULL, "Set drum storage to 832 tracks" },
  { UNIT_DRMSIZE, 896, NULL, "896",
    &drm_set_size, NULL, NULL, "Set drum storage to 896 tracks" },
  { UNIT_DRMSIZE, 960, NULL, "960",
    &drm_set_size, NULL, NULL, "Set drum storage to 960 tracks" },
  { UNIT_DRMSIZE, 1024, NULL, "1024",
    &drm_set_size, NULL, NULL, "Set drum storage to 1024 tracks" },
  { 0 }
};

DEBTAB drm_deb[] = {
  { "TRACE",       DBG_DTRACE,     "Trace device I/O requests" },
  { "STATE",       DBG_DSTATE,     "Display device state changes" },
  { "INTR",        DBG_DINTR,      "Display device interrupt requests" },
  { "ERROR",       DBG_DERROR,     "Display device errors" },
  { "LOCATION",    DBG_DLOC,       "Display address for I/O instructions" },
  { "FIRSTREJ",    DBG_DFIRSTREJ,  "Suppress display of 2nd ... I/O rejects" },
  { "ALL", DBG_DTRACE | DBG_DSTATE | DBG_DINTR | DBG_DERROR | DBG_DLOC },
  { NULL }
};

DEVICE drm_dev = {
  "DRM", &drm_unit, drm_reg, drm_mod,
  1, 10, 31, 1, 8, 8,
  NULL, NULL, &drm_reset,
  NULL, &drm_attach, &drm_detach,
  &DRMdev,
  DEV_DEBUG | DEV_DISK | DEV_DISABLE | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT,
  0, drm_deb,
  NULL, NULL, &drm_help, NULL, NULL, NULL
};

/*
 * Dump the current internal state of the DRM device.
 */
const char *DRMStateStr[] = {
  "Idle", "Write", "Read"
};

void DRMstate(const char *where, DEVICE *dev, IO_DEVICE *iod)
{
  fprintf(DBGOUT,
          "%s[%s %s: %s, Func: %04X, Sta: %04X, Ena: %04X]\r\n",
          INTprefix, dev->name, where, DRMStateStr[iod->iod_state],
          iod->FUNCTION, iod->STATUS, iod->IENABLE);
  fprintf(DBGOUT,
          "%s[%s: ISA: %04X, ICA: %04X, FCA: %04X, SAS: %04X, CAS: %04X]\r\n",
          INTprefix, dev->name,
          iod->iod_isa, iod->iod_ica, iod->iod_fca,
          iod->SASTATUS, iod->CASTATUS);
  fprintf(DBGOUT,
          "%s[%s: Trk: %03X, Sec: %02X, Cur: %04X, Comp: %c]\r\n",
          INTprefix, dev->name, iod->iod_trk, iod->iod_sec, iod->iod_ca,
          iod->iod_compare ? 'T' : 'F');
}

/*
 * Load and validate drum address in the Initial Sector Address Register
 */
static t_bool LoadDrumAddress(void)
{
  uint16 track = (DRMdev.iod_isa & DRM_TRK_MASK) >> DRM_TRK_SHIFT;

  if (track >= DRMdev.iod_tracks)
    return FALSE;

  /*
   * The sector field initially holds (sector # - 1) and needs to be
   * incremented without touching the track #.
   */
  DRMdev.iod_trk = track;
  DRMdev.iod_sec = (DRMdev.iod_isa + 1) & DRM_SEC_MASK;
  return TRUE;
}

/*
 * Set up a drum I/O operation using the currently set parameters
 */
static void StartDrumIO(t_bool wr)
{
  if (LoadDrumAddress()) {
    DRMdev.iod_compare =(DRMdev.iod_fca >= DRMdev.CASTATUS) &&
      (DRMdev.iod_fca < (DRMdev.CASTATUS + DRM_NUMWD));
    DRMdev.iod_ca = DRMdev.iod_ica;
    DRMdev.CASTATUS = DRMdev.iod_compare ? DRMdev.iod_ica : DRMdev.iod_fca;

    fw_IOunderwayEOP2(&DRMdev, IO_ST_DATA);

    if ((drm_dev.dctrl & DBG_DTRACE) != 0) {
      fprintf(DBGOUT,
              "%sDRM - Start %s I/O, Trk: %03X, Sec: %02X, Start: %04X, End: %04X\r\n",
              INTprefix, wr ? "Write" : "Read",
              DRMdev.iod_trk, DRMdev.iod_sec, DRMdev.iod_ica, DRMdev.iod_fca);
    }

    DRMdev.iod_state = wr ? DRM_WRITE : DRM_READ;
    sim_activate(&drm_unit, DRM_ACCESS_WAIT);
    return;
  }

  /*
   * Generate a sector overrange error and possible interrupt.
   */
  DRMdev.STATUS &= ~IO_ST_DATA;
  DRMdev.STATUS |= IO_1752_OVERR;
  fw_IOalarm(FALSE, &drm_dev, &DRMdev, "Invalid track #");
}

/*
 * Increment the drum sector address.
 */
static void DrumIOIncSector(void)
{
  DRMdev.iod_sec = (DRMdev.iod_sec + 1) & DRM_SEC_MASK;
  if (DRMdev.iod_sec == 0)
    DRMdev.iod_trk++;
}

/*
 * Initiate a read operation on the drum.
 */
static enum drmio_status DrumIORead(UNIT *uptr)
{
  uint16 buf[DRM_NUMWD];
  uint32 lba = (DRMdev.iod_trk << DRM_TRK_SHIFT) | DRMdev.iod_sec;
  int i;

  if (DRMdev.iod_trk >= DRMdev.iod_tracks)
    return DRMIO_ADDRERR;

  /*
   * Report any error in the underlying container infrastructure as an
   * address error.
   */
  if (sim_fseeko(uptr->fileref, lba * DRM_NUMBY, SEEK_SET) ||
      (sim_fread(buf, sizeof(uint16), DRM_NUMWD, uptr->fileref) != DRM_NUMWD))
    return DRMIO_ADDRERR;

  for (i = 0; i < DRM_NUMWD; i++) {
    /*** TODO: fix protect check ***/
    if (!IOStoreToMem(DRMdev.iod_ca, buf[i], TRUE))
      return DRMIO_PROTECT;

    DRMdev.DATASTATUS = buf[i];

    if (DRMdev.iod_ca++ == DRMdev.iod_fca) {
      DRMdev.CASTATUS = DRMdev.iod_ca;
      DrumIOIncSector();
      return DRMIO_DONE;
    }
  }
  DrumIOIncSector();
  if ((drm_dev.dctrl & DBG_DTRACE) != 0)
    fprintf(DBGOUT,
            "%sDRM - Continue Read I/O, Trk: %03X, Sec: %02X, Cur: %04X, End: %04X\r\n",
            INTprefix, DRMdev.iod_trk, DRMdev.iod_sec,
            DRMdev.iod_ca, DRMdev.iod_fca);

  return DRMIO_MORE;
}

/*
 * Initiate a write operation on the drum.
 */
static enum drmio_status DrumIOWrite(UNIT *uptr)
{
  uint16 buf[DRM_NUMWD];
  uint32 lba = (DRMdev.iod_trk << DRM_TRK_SHIFT) | DRMdev.iod_sec;
  t_bool done = FALSE;
  int i;

  if (DRMdev.iod_trk >= DRMdev.iod_tracks)
    return DRMIO_ADDRERR;

  memset(buf, 0, sizeof(buf));

  for (i = 0; i < DRM_NUMWD; i++) {
    DRMdev.DATASTATUS = buf[i] = LoadFromMem(DRMdev.iod_ca);
    if (DRMdev.iod_ca++ == DRMdev.iod_fca) {
      DRMdev.CASTATUS = DRMdev.iod_ca;
      done = TRUE;
      break;
    }
  }

  /*
   * Report any error in the underlying container infrastructure as an
   * address error.
   */
  if (sim_fseeko(uptr->fileref, lba * DRM_NUMBY, SEEK_SET) ||
      (sim_fwrite(buf, sizeof(uint16), DRM_NUMWD, uptr->fileref) != DRM_NUMWD))
    return DRMIO_ADDRERR;

  DrumIOIncSector();
  if (((drm_dev.dctrl & DBG_DTRACE) != 0) && !done)
    fprintf(DBGOUT,
            "%sDRM - Continue Write I/O, Trk: %03X, Sec: %02X, Cur: %04X, End: %04X\r\n",
            INTprefix, DRMdev.iod_trk, DRMdev.iod_sec,
            DRMdev.iod_ca, DRMdev.iod_fca);

  return done ? DRMIO_DONE : DRMIO_MORE;
}

/*
 * Perform read/write sector operations from within the unit service routine.
 */
void DrumIO(UNIT *uptr, uint8 iotype)
{
  const char *error = "Unknown";
  enum drmio_status status = DRMIO_ADDRERR;

  switch (iotype) {
    case DRM_WRITE:
      status = DrumIOWrite(uptr);
      break;

    case DRM_READ:
      status = DrumIORead(uptr);
      break;
  }

  /*
   * Update the sector address status register if the I/O was successful.
   * Note that since we perform sector at a time I/O, we assert the "Core
   * Address Compare" bit for the entire period.
   */
  if ((status == DRMIO_MORE) || (status == DRMIO_DONE)) {
    DRMdev.iod_compare =(DRMdev.iod_fca >= DRMdev.iod_ca) &&
      (DRMdev.iod_fca < (DRMdev.iod_ca + DRM_NUMWD));
  }

  switch (status) {
    case DRMIO_MORE:
      sim_activate(uptr, DRM_SECTOR_WAIT);
      break;

    case DRMIO_PROTECT:
      DRMdev.STATUS |= IO_1752_PROTF;
      error = "Protection Fault";
      goto err;

    case DRMIO_ADDRERR:
      DRMdev.STATUS |= IO_1752_OVERR;
      error = "Address Error";
  err:
      DRMdev.iod_compare = FALSE;
      DRMdev.iod_state = DRM_IDLE;

      if ((drm_dev.dctrl & DBG_DERROR) != 0)
        fprintf(DBGOUT,
                "%sDRM - Read/Write failed - %s\r\n",
                INTprefix, error);

      fw_IOalarm(FALSE, &drm_dev, &DRMdev, "Alarm");
      break;

    case DRMIO_DONE:
      DRMdev.iod_compare = FALSE;
      DRMdev.iod_event = Instructions;
      DRMdev.iod_state = DRM_IDLE;

      if ((drm_dev.dctrl & DBG_DTRACE) != 0)
        fprintf(DBGOUT,
                "%sDRM - Read/Write transfer complete\r\n", INTprefix);

      DRMdev.STATUS |= IO_ST_DATA;
      fw_IOcompleteEOP2(FALSE, &drm_dev, &DRMdev, 0xFFFF, "Transfer complete");
      break;
  }
}

/* Unit service */

t_stat drm_svc(UNIT *uptr)
{
  if ((drm_dev.dctrl & DBG_DTRACE) != 0) {
    fprintf(DBGOUT, "%s[DRM: drm_svc() entry]\r\n", INTprefix);
    if ((drm_dev.dctrl & DBG_DSTATE) != 0)
      DRMstate("svc_entry", &drm_dev, &DRMdev);
  }

  switch (DRMdev.iod_state) {
    case DRM_IDLE:
      /*
       * Unit is idle, nothing to do.
       */
      break;

    case DRM_WRITE:
    case DRM_READ:
      DrumIO(uptr, DRMdev.iod_state);
      break;
  }

  if ((drm_dev.dctrl & DBG_DTRACE) != 0) {
    fprintf(DBGOUT, "%s[DRM: drm_svc() exit]\r\n", INTprefix);
    if ((drm_dev.dctrl & DBG_DSTATE) != 0)
      DRMstate("svc_exit", &drm_dev, &DRMdev);
  }
  return SCPE_OK;
}

/* Reset routine */

t_stat drm_reset(DEVICE *dptr)
{
  DRMdev.STATUS = 0;
  if ((drm_unit.flags & UNIT_ATT) != 0)
    DRMdev.STATUS |= IO_ST_READY | IO_ST_DATA;

  DRMdev.SASTATUS =
    DRMdev.CASTATUS =
    DRMdev.DATASTATUS = 0;

  DRMdev.iod_trk =
    DRMdev.iod_sec = 0;

  DRMdev.iod_event = Instructions;

  return SCPE_OK;
}

/* Attach routine */

t_stat drm_attach (UNIT *uptr, CONST char *cptr)
{
  t_addr capac = uptr->capac;
  t_stat r;
  t_offset tracks;

  r = attach_unit(uptr, cptr);
  if (r != SCPE_OK)
    return r;

  /*
   * If this ia a newly created file, set the drum size appropriately.
   */
  if (sim_fsize_ex(uptr->fileref) == 0)
    sim_set_fsize(uptr->fileref, capac);

  /*
   * If we are attaching to an existing file, make sure that its size:
   *
   *   - is a multiple of 3072 words
   *   - is between 64 and 1024 tracks (each of 3072 words)
   *   - is a multiple of 64 tracks
   */
  tracks = sim_fsize_ex(uptr->fileref) / (DRM_NUMSC * DRM_NUMBY);
  if (((sim_fsize_ex(uptr->fileref) % (DRM_NUMSC * DRM_NUMBY)) != 0) ||
      (tracks < DRM_MINTRACKS) ||
      (tracks > DRM_MAXTRACKS) ||
      ((tracks & (DRM_MINTRACKS - 1)) != 0)) {
    detach_unit(uptr);
    uptr->capac = capac;
    return sim_messagef(SCPE_OPENERR, "Invalid file size");
  }
  DRMdev.STATUS = IO_ST_READY | IO_ST_DATA;
  DRMdev.iod_tracks = (uint16)tracks;
  DRMdev.iod_event = Instructions;

  return SCPE_OK;
}

/* Detach routine */

t_stat drm_detach(UNIT *uptr)
{
  sim_cancel(uptr);
  DRMdev.STATUS &= ~(IO_ST_READY | IO_ST_DATA);
  return detach_unit(uptr);
}

/*
 * Change drum capacity
 */
t_stat drm_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
  if ((uptr->flags & UNIT_ATT) != 0)
    return SCPE_ALATT;

  drm_unit.capac = val * DRM_NUMSC * DRM_NUMBY;
  DRMdev.iod_tracks = val;
  return SCPE_OK;
}

/* Check if I/O should be rejected */

t_bool DRMreject(IO_DEVICE *iod, t_bool output, uint8 reg)
{
  if (output) {
    if (reg != 0x1)
      return (DRMdev.STATUS & IO_ST_BUSY) != 0;
  }
  return FALSE;
}

/* Perform I/O */

enum IOstatus DRMin(IO_DEVICE *iod, uint8 reg)
{
  /*
   * The I/O framework passes input requests for the Director Status register
   * and the Sector Address Status register so that we can return values
   * which are dependent on the rotational position of the drum.
   */
  
  /*
   * Update the current sector value.
   */
  if (DRMdev.iod_state == DRM_IDLE) {
    t_uint64 sectors = (Instructions - DRMdev.iod_event) / DRM_SECTOR_WAIT;
    
    if (sectors != 0) {
      DRMdev.iod_sec = (DRMdev.iod_sec + sectors) & DRM_SEC_MASK;
      DRMdev.iod_event += sectors * DRM_SECTOR_WAIT;
    }
  }

  switch (reg) {
    case 0x01:
      /*
       * Director Status
       */
      if (DRMdev.iod_sec == (DRMdev.iod_isa & DRM_SEC_MASK))
        DRMdev.STATUS |= IO_1752_SECCMP;
      else DRMdev.STATUS &= ~IO_1752_SECCMP;
      Areg = DRMdev.STATUS;
      return IO_REPLY;

    case 0x02:
      /*
       * Sector Address Status
       */
      DRMdev.SASTATUS = (DRMdev.iod_trk << DRM_TRK_SHIFT) | DRMdev.iod_sec;
      if (DRMdev.iod_compare)
        DRMdev.SASTATUS |= DRM_COMPARE;
      Areg = DRMdev.SASTATUS;
      return IO_REPLY;
  }
  return IO_REJECT;
}

enum IOstatus DRMout(IO_DEVICE *iod, uint8 reg)
{
  switch (reg) {
    case 0x00:
      /*
       * Initiate Drum Write Operation
       */
      StartDrumIO(TRUE);
      break;

    case 0x01:
      /*
       * Director function.
       */
      doDirectorFunc(&drm_dev, FALSE);
      break;

    case 0x04:
      /*
       * Initiate Drum Read Operation
       */
      StartDrumIO(FALSE);
      break;

    case 0x08:
      /*
       * Load Initial Sector Address Register.
       */
      DRMdev.iod_isa = IOAreg;
      DRMdev.iod_trk = (IOAreg & DRM_TRK_MASK) >> DRM_TRK_SHIFT;
      break;

    case 0x0C:
      /*
       * Load Initial Core Address Register
       */
      DRMdev.iod_ica = DRMdev.CASTATUS = IOAreg;
      DRMdev.iod_compare = DRMdev.iod_ica == DRMdev.iod_fca;
      break;

    case 0x0E:
      /*
       * Load Final Core Address Register
       */
      DRMdev.iod_fca = IOAreg;
      DRMdev.iod_compare = DRMdev.iod_ica == DRMdev.iod_fca;
      break;
  }
  /*
   * Any non-rejected output clears EOP interrupt.
   */
  if ((DRMdev.STATUS & IO_ST_EOP) != 0)
    DRMdev.STATUS &= ~(IO_ST_INT | IO_ST_EOP);
  rebuildPending();

  return IO_REPLY;
}

/*
 * Device clear routine. Clear controller operation from a director
 * function - same as device reset but don't clear CASTATUS and DATASTATUS
 */
void DRMclear(DEVICE *dptr)
{
  DRMdev.STATUS = 0;
  if ((drm_unit.flags & UNIT_ATT) != 0)
    DRMdev.STATUS |= IO_ST_READY | IO_ST_DATA;

  DRMdev.SASTATUS = 0;

  DRMdev.iod_trk =
    DRMdev.iod_sec = 0;

  DRMdev.iod_event = Instructions;
}

/*
 * Address decode routine. If bit 0 of an output  register address is set,
 * clear bits 1 - 7 since they are ignored.
 */
uint8 DRMdecode(IO_DEVICE *iod, t_bool output, uint8 reg)
{
  if (output && ((reg & 0x01) != 0))
    reg &= 0x01;
  return reg;
}

/*
 * Autoload support
 */
t_stat DRMautoload(void)
{
  UNIT *uptr = &drm_unit;

  if ((uptr->flags & UNIT_ATT) != 0) {
    uint32 i;

    for (i = 0; i < DRM_AUTOLOAD; i++) {
      t_offset offset = i * DRM_NUMBY;
      void *buf = &M[i * DRM_NUMBY];

      if (sim_fseeko(uptr->fileref, offset, SEEK_SET) ||
          (sim_fread(buf, sizeof(uint16), DRM_NUMWD, uptr->fileref) != DRM_NUMWD))
        return SCPE_IOERR;
    }
    return SCPE_OK;
  }
  return SCPE_UNATT;
}

t_stat drm_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
  const char helpString[] =
    /****************************************************************************/
    " The %D device is a 1752 drum memory controller.\n"
    "1 Hardware Description\n"
    " The 1752-1/2/3/4 consists of a controller and field expandable drum\n"
    " storage from 196608 to 1572864 words. A custom order may provide\n"
    " additional storage up to 3145728 words.\n"
    "2 Equipment Address\n"
    " Drum controllers are typically set to equipment address 2. This address\n"
    " may be changed by:\n\n"
    "+sim> SET %D EQUIPMENT=hexValue\n\n"
    "2 $Registers\n"
    "\n"
    " These registers contain the emulated state of the device. These values\n"
    " don't necessarily relate to any detail of the original device being\n"
    " emulated but are merely internal details of the emulation. STATUS always\n"
    " contains the current status of the device as it would be read by an\n"
    " application program.\n"
    "1 Configuration\n"
    " A %D device is configured with various simh SET and ATTACH commands\n"
    "2 $Set commands\n";

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