/*

   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_mt.c: 1732-A/B and 1732-3 magtape device support
 *               Simh devices: mt0, mt1, mt2, mt3
 */

/*
 * Notes:
 *
 * 1. When writing a tape record in direct mode (programmed I/O), the
 * documentation states "Whenever the computer breaks the continuity of the
 * computer word outputs, the controller initiates an End of Record sequence."
 *
 * Since there is no explicit command sequence to initiate an End of Record
 * operation, we need to estimate how many instructions to delay after a
 * data OUT operation before terminating the current record. The tape drives
 * operate at a maximum of 37.5 inches per second, so given the recording
 * density we can compute the maximum output data rate and hence the time
 * delay between 2 characters written to the tape. In addition, since we are
 * emulating a 1732 controller, we need to take into account the character
 * assembly operating mode where each data OUT instruction writes 2 chatacters
 * to the tape. If we assume an average execution time of 1 microsecond per
 * instruction (to keep the arithmetic simple), we get the following table:
 *
 *      Density (bpi)   Data Rate       Instr. Delay    Char Assembly Delay
 *
 *       200             7.50K char/sec 134 Instrs.     268 Instrs.
 *       556            20.85K char/sec  48 Instrs.      96 Instrs.
 *       800            30.00K char/sec  33 Instrs.      66 Instrs.
 *      1600            60.00K char/sec  16 Instrs.      33 Instrs.
 *
 * The emulation does not need to be very strict with regard to timing:
 *
 *      - Using instruction counts is not a very accurate representation of
 *        real time. 1784-2 instruction execution times range from 0.600 to
 *        12.8 uSec so timing can easily be off by almost a factor of 2.
 *        See definition of LOOSETIMING below.
 *
 *      - This does mean that SMM17 timing diagnostics may fail since SMM
 *        uses a timing loop of it's own.
 *
 * The PET diagnostic implies that the interrupt rate is too high for 1600 BPI
 * access to be supported in direct mode.
 *
 * 2. This driver supports 3 modes of access:
 *
 *      1 - Direct mode (programmed I/O)
 *      2 - Buffered mode (DSA or DMA mode)
 *      3 - 1706 buffered data channel access
 *
 * The buffered data channel access is only supported if the controller is
 * configured as a 1732-A (set mt type=1732-A).
 *
 */
#include <string.h>
#include "cdc1700_defs.h"
#include "sim_tape.h"

#define LOOSETIMING(t)  (((t) * 3)/ 2)

#define DEVTYPE_1732_A  IOtype_dev1             /* Controller is 1732-A */
#define DEVTYPE_1732_3  IOtype_dev2             /* Controller is 1732-3 */

#define STATUS2         iod_readR[2]
#define BUFFEREDIO      iod_writeR[3]
#define CURADDRESS      iod_readR[3]

extern char INTprefix[];

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

extern t_bool doDirectorFunc(DEVICE *, t_bool);
extern void fw_IOcompleteEOP(t_bool, DEVICE *, IO_DEVICE *, uint16, const char *);
extern void fw_IOunderwayEOP(IO_DEVICE *, uint16);
extern void fw_IOintr(t_bool, DEVICE *, IO_DEVICE *, uint16, uint16, uint16, const char *);
extern t_bool fw_reject(IO_DEVICE *, t_bool, uint8);
extern void fw_setForced(IO_DEVICE *, uint16);
extern void fw_clearForced(IO_DEVICE *, uint16);

extern void loadBootstrap(uint16 *, int, uint16, uint16);

extern t_stat checkReset(DEVICE *, uint8);

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_stoponrej(UNIT *, int32, CONST char *, void *);
extern t_stat clr_stoponrej(UNIT *, int32, CONST char *, void *);

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

extern void buildDCtables(void);

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

extern t_bool IOFWinitialized;

extern UNIT cpu_unit;

t_stat mt_show_transport(FILE *, UNIT *, int32, CONST void *);
t_stat mt_set_9track(UNIT *, int32, CONST char *, void *);
t_stat mt_set_7track(UNIT *, int32, CONST char *, void *);
t_stat mt_show_type(FILE *, UNIT *, int32, CONST void *);
t_stat mt_set_type(UNIT *, int32, CONST char *, void *);

#define DENS    u3

/*
 * Nine-track magnetic tape bootstrap
 */
static uint16 mtbootstrap9[] = {
  0x6819,                       /* 00:  STA*  $19   */
  0x6819,                       /* 01:  STA*  $19   */
  0xE000,                       /* 02:  LDQ+  $382  */
  0x0382,                       /*       0x1382 for 1706 No. 1 */
  0xC813,                       /* 04:  LDA*  $13   */
  0x03FE,                       /* 05:  OUT   $-1   */
  0x0DFE,                       /* 06:  INQ   $-1   */
  0xC811,                       /* 07:  LDA*  $11   */
  0x03FE,                       /* 08:  OUT   $-1   */
  0x0DFE,                       /* 09:  INQ   $-1   */
  0x0203,                       /* 0A:  INP   $03   */
  0x6C0F,                       /* 0B:  STA*  ($0F) */
  0xD80E,                       /* 0C:  RAO*  $0E   */
  0x18FC,                       /* 0D:  JMP*  $FC   */
  0x0D01,                       /* 0E:  INQ   $1    */
  0x0B00,                       /* 0F:  NOP         */
  0x02FE,                       /* 10:  INP   $-1   */
  0x0FCB,                       /* 11:  ALS   $0B   */
  0x0131,                       /* 12:  SAM   $1    */
  0x18F5,                       /* 13:  JMP*  $F5   */
  0xC804,                       /* 14:  LDA*  $04   */
  0x03FE,                       /* 15:  OUT   $-1   */
  0x1C03,                       /* 16:  JMP*  ($03) */
  0x044C,                       /* 17:        DATA  */
  0x0100,                       /* 18:              */
  0x0000,                       /* 19:              */
  0x0000                        /* 1A:              */
};
#define MTBOOTLEN9      (sizeof(mtbootstrap9) / sizeof(uint16))

#if 0
/*
 * Seven-track magnetic tape bootstrap
 */
static uint16 mtbootstrap7[] = {
  0x0500,                       /* 00:  IIN         */
  0x6824,                       /* 01:  STA*  $24   */
  0x6824,                       /* 02:  STA*  $24   */
  0xE000,                       /* 03:  LDQ+  $0382 */
  0x0382,                       /*       0x1382 for 1706 No. 1 */
  0xC81E,                       /* 05:  LDA*  $1E   */
  0x03FE,                       /* 06:  OUT   $-1   */
  0x0DFE,                       /* 07:  INQ   $-1   */
  0xC81C,                       /* 08:  LDA*  $1C   */
  0x03FE,                       /* 09:  OUT   $-1   */
  0x0DFE,                       /* 0A:  INQ   $-1   */
  0x0A00,                       /* 0B:  ENA   $00   */
  0x020D,                       /* 0C:  INP   $0D   */
  0x0FCA,                       /* 0D:  ALS   $0A   */
  0x0821,                       /* 0E:              */
  0x0A00,                       /* 0F:  ENA   $00   */
  0x02FE,                       /* 10:  INP   $-1   */
  0x0FC4,                       /* 11:  ALS   $04   */
  0x0869,                       /* 12:  EAM   M     */
  0x0A00,                       /* 13:  ENA   $00   */
  0x02FE,                       /* 14:  INP   $-1   */
  0x0F42,                       /* 15:  ARS   $02   */
  0x086C,                       /* 16:  EAM   A     */
  0x6C0F,                       /* 17:  STA*  ($0F) */
  0xD80E,                       /* 18:  RAO*  $0E   */
  0x18F1,                       /* 19:  JMP*  $F1   */
  0x0D01,                       /* 1A:              */
  0x0B00,                       /* 1B:              */
  0x02FE,                       /* 1C:              */
  0x0FCB,                       /* 1D:              */
  0x0131,                       /* 1E:              */
  0x18EA,                       /* 1F:              */
  0xC804,                       /* 20:              */
  0x03FE,                       /* 21:              */
  0x1C03,                       /* 22:              */
  0x0414,                       /* 23:              */
  0x0100,                       /* 24:              */
  0x0000,                       /* 25:              */
  0x0000                        /* 26:              */
};
#define MTBOOTLEN7      (sizeof(mtbootstrap7) / sizeof(uint16))
#endif

/*
 * SMM17 bootstraps
 */
static uint16 smm17boot9[] = {
  0x68FE,                       /* xFE0: MTBOOT STA*    *-1             */
  0xE000,                       /* xFE1:        LDQ     =N$WESD         */
  0x0382,                       /* xFE2: EQUIP  $382                    */
  0xC000,                       /* xFE3:        LDA     =N$44C          */
  0x044C,
  0x03FE,                       /* xFE5:        OUT     -1              */
  0x09B3,                       /* xFE6:        INA     -$400-$44C      */
  0x0DFE,                       /* xFE7:        INQ     -1              */
  0x03FE,                       /* xFE8:        OUT     -1              */
  0x0F42,                       /* xFE9:        ARS     2               */
  0x03FE,                       /* xFEA:        OUT     -1              */
  0x0DFE,                       /* xFEB:        INQ     -1              */
  0x02FE,                       /* xFEC: MT1    INP     -1              */
  0x6CF1,                       /* xFED:        STA*    (MTBOOT-1)      */
  0x0102,                       /* xFEE:        SAZ     ENDBT-*-1       */
  0xD8EF,                       /* xFEF:        RAO*    MTBOOT-1        */
  0x18FB,                       /* xFF0:        JMP*    MT1             */
  0x1007                        /* xFF1: ENDBT  JMP-    QL ENTRY        */
};
#define SMM17BOOTLEN9   (sizeof(smm17boot9) / sizeof(uint16))

#if 0
static uint16 smm17boot7[] = {
  0x68FE,                       /* xFE0: MTBOOT STA*    *-1             */
  0xE000,                       /* xFE1:        LDQ     =N$WESD         */
  0x0382,                       /* xFE2: EQUIP  $382                    */
  0xC000,                       /* xFE3:        LDA     =N$405          */
  0x0405,
  0x03FE,                       /* xFE5:        OUT     -1              */
  0x09FB,                       /* xFE6:        INA     -4              */
  0x0DFE,                       /* xFE7:        INQ     -1              */
  0x03FE,                       /* xFE8:        OUT     -1              */
  0x0F42,                       /* xFE9:        ARS     2               */
  0x03FE,                       /* xFEA:        OUT     -1              */
  0x0DFE,                       /* xFEB:        INQ     -1              */
  0x0A00,                       /* xFEC:        ENA     0               */
  0x1807,                       /* xFED:        JMP-    MT2             */
  0x02FE,                       /* xFEE: MT1    INP     -1              */
  0x0F42,                       /* xFEF:        ARS     2               */
  0xBCEE,                       /* xFF0:        EOR*    (MTBOOT-1)      */
  0x010A,                       /* xFF1:        SAZ     ENDBT-*-1       */
  0x7CEC,                       /* xFF2:        SPA*    (MTBOOT-1)      */
  0xD8EB,                       /* xFF3:        RAO*    MTBOOT-1        */
  0x02FE,                       /* xFF4: MT2    INP     -1              */
  0x0FCA,                       /* xFF5:        ALS     10              */
  0x7CE8,                       /* xFF6:        SPA*    (MTBOOT-1)      */
  0x02FE,                       /* xFF7:        INP     -1              */
  0x0FC4,                       /* xFF8:        ALS     4               */
  0xBCE5,                       /* xFF9:        EOR*    (MTBOOT-1)      */
  0x7CE4,                       /* xFFA:        SPA*    (MTBOOT-1)      */
  0x18F2,                       /* xFFB:        JMP*    MT1             */
  0x1007                        /* xFFC:        JMP-    QL ENTRY        */
};
#define SMM17BOOTLEN7   (sizeof(smm17boot7) / sizeof(uint16))
#endif

/*
 * Shared I/O buffer. Note that this is larger than the max possible memory
 * so the only way to handle such large records is to use non-DMA with
 * dynamic processing of the data.
 */
#define MTSIZ           131072
uint8 MTbuf[MTSIZ];
t_mtrlnt MToffset, MTremain;
static enum  { MT_IDLE, MT_READING, MT_WRITING, MT_READTMO, MT_WRITETMO, MT_DSADONE } MTmode;

t_stat mt_svc(UNIT *);
t_stat mt_reset(DEVICE *);
t_stat mt_boot(int32, DEVICE *);
t_stat mt_attach(UNIT *, CONST char *);
t_stat mt_detach(UNIT *);

void MTstate(const char *, DEVICE *, IO_DEVICE *);
void MTclear(DEVICE *);
t_bool MTreject(IO_DEVICE *, t_bool, uint8);
enum IOstatus MTin(IO_DEVICE *, uint8);
enum IOstatus MTout(IO_DEVICE *, uint8);
enum IOstatus MTBDCin(IO_DEVICE *, uint16 *, uint8);
enum IOstatus MTBDCout(IO_DEVICE *, uint16 *, uint8);

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

/*
        1732-3 Magnetic Tape Controller

   Addresses
                                Computer Instruction
   Q Register         Output From A        Input to A

      00                Write                Read
      01                Control Function     Director Status 1
      10                Unit Select          Director Status 2
      11                Buffered I/O         Current Address

  Operations:

  Control Function

    15              11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X |   |   |   |   | X | X |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                         |           |           |   |   |   |   |
                         +-----------+           |   |   |   |   Clr Controller
                               |                 |   |   |   Clr Interrupts
                               |                 |   |   Data Interrupt Req.
                               |                 |   Interrupt on EOP
                               |                 Interrupt on Alarm
                               |
                               Motion Control:
                                   0001 Write Motion
                                   0010 Read Motion
                                   0011 Backspace
                                   0101 Write File Mark/Tape Mark
                                   0110 Search File Mark/Tape Mark Forward
                                   0111 Search File Mark/Tape Mark Backward
                                   1000 Rewind Load
                                   1100 Rewind Unload (1732-A only)

  Unit Select

    15          12  11  10   9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X |   |   |   |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                 |   |   |   |   |   |   |   |   |   |   |   |   |
                 |   |   |   |   +---+   |   |   |   |   |   |   Character Mode
                 |   |   |   |     |     |   |   |   |   |   BCD mode
                 |   |   |   |     |     |   |   |   |   Binary mode
                 |   |   |   |     |     |   |   |   Select 800 BPI
                 |   |   |   |     |     |   |   Select 556 BPI
                 |   |   |   |     |     |   Select 1600 BPI (1732-A 200 BPI)
                 |   |   |   |     |     Assembly/Disassembly
                 |   |   |   |     Tape Unit (0-3)
                 |   |   |   (1732-A only, additional unit select bit)
                 |   |   Select Tape Unit
                 |   Deselect Tape Unit
                 Select Low Read Threshold (1732-3 only)

  Status Response:

  Director Status 1

    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
     |   |   |   |   |   |   |   Parity Error
     |   |   |   |   |   |   End of Tape
     |   |   |   |   |   Beginning of Tape
     |   |   |   |   File Mark
     |   |   |   Controller Active
     |   |   Fill
     |   Storage Parity Error (1732-3 only)
     Protect Fault (1732-3 only)

  Director Status 2 

    15                       9   8   7   6   5   4   3   2   1   0
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X | X | X | X | X |   |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
                             |   |   |   |   |   |   |   |   |   |
                             |   |   |   |   |   |   |   |   |   556 BPI
                             |   |   |   |   |   |   |   |   800 BPI
                             |   |   |   |   |   |   |   1600 BPI (1732-3 only)
                             |   |   |   |   |   |   Seven Track
                             |   |   |   |   |   Write Enable
                             |   |   |   |   PE - Warning
                             |   |   |   PE - Lost Data
                             |   |   PE - transport
                             |   ID - Abort
                             Low Read Threshold

*/

IO_DEVICE MTdev = IODEV(NULL, "Magtape", 1732, 7, 0xFF, 0,
                        MTreject, MTin, MTout, MTBDCin, MTBDCout,
                        MTstate, NULL, NULL, MTclear, NULL, NULL,
                        0x7F, 4,
                        MASK_REGISTER0 | MASK_REGISTER1 | MASK_REGISTER2 | \
                        MASK_REGISTER3,
                        MASK_REGISTER1 | MASK_REGISTER2 | MASK_REGISTER3,
                        0, 0, 0, 1, NULL);

/*
 * Define usage for "private" IO_DEVICE data areas.
 */
#define iod_mode        iod_private             /* operating mode */
#define iod_delay       iod_private3            /* current delay reason */
#define iod_wasWriting  iod_private4            /* writing was in progress */
#define iod_reason      iod_private5            /* reason for EOP */
#define iod_CWA         iod_readR[3]            /* current DSA address */
#define iod_LWA         iod_private6            /* last word address */
#define iod_DSApending  iod_private10           /* DSA request pending */
#define iod_FWA         iod_private11           /* first word address */

/*
 * Define delay functions other than the standard motion commands. The low
 * 7 bits are available, zero is reserved to mean no pending delay.
 */
#define IO_DELAY_RDATA  0x01                    /* Delay IO_ST_DATA for read*/
#define IO_DELAY_WDATA  0x02                    /*    and write */
#define IO_DELAY_RTMO   0x03                    /* Read record timeout */
#define IO_DELAY_WTMO   0x04                    /* Write record timeout */
#define IO_DELAY_EOP    0x05                    /* EOP delay */
#define IO_DSA_READ     0x06                    /* DSA Read operation */
#define IO_DSA_WRITE    0x07                    /* DSA Write operation  */
#define IO_LOCAL_MASK   0x7F

/* MT data structures

   mt_dev       MT device descriptor
   mt_unit      MT units
   mt_reg       MT register list
   mt_mod       MT modifier list
*/

#define MT_NUMDR        4                       /* # drives */

UNIT mt_unit[] = {
  { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
  { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
  { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE, 0) },
  { UDATA(&mt_svc, UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+UNIT_7TRACK, 0) },
};

REG mt_reg_1732_A[] = {
  { HRDATAD(FUNCTION, MTdev.FUNCTION, 16, "Last director status issued") },
  { HRDATAD(STATUS, MTdev.STATUS, 16, "Director status register") },
  { HRDATAD(UNITSEL, MTdev.iod_writeR[2], 16, "Last Unit Select issued") },
  { HRDATAD(STATUS2, MTdev.STATUS2, 16, "Transport status register") },
  { HRDATAD(IENABLE, MTdev.IENABLE, 16, "Interrupts enabled") },
  { NULL }
};

REG mt_reg_1732_3[] = {
  { HRDATAD(FUNCTION, MTdev.FUNCTION, 16, "Last director status issued") },
  { HRDATAD(STATUS, MTdev.STATUS, 16, "Director status register") },
  { HRDATAD(UNITSEL, MTdev.iod_writeR[2], 16, "Last Unit Select issued") },
  { HRDATAD(STATUS2, MTdev.STATUS2, 16, "Transport status register") },
  { HRDATAD(IENABLE, MTdev.IENABLE, 16, "Interrupts enabled") },
  { HRDATAD(BUFFEREDIO, MTdev.BUFFEREDIO, 16, "Last Buffered I/O issued") },
  { HRDATAD(CURADDRESS, MTdev.CURADDRESS, 16, "Current DSA address") },
  { HRDATAD(LASTADDRESS, MTdev.iod_LWA, 16, "Last DSA address") },
  { NULL }
};

MTAB mt_mod[] = {
  { MTAB_XTD|MTAB_VDV, 0, "TYPE", "TYPE={1732-A|1732-3}",
    &mt_set_type, &mt_show_type, NULL, "Set/Display magtape controller type" },
  { MTAB_XTD|MTAB_VDV, 0, "EQUIPMENT", "EQUIPMENT=hexAddress",
    &set_equipment, &show_addr, NULL, "Set/Display equipment address" },
  { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", 
        &set_writelock, &show_writelock,   NULL, "Write enable tape drive" },
  { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", 
        &set_writelock, NULL,   NULL, "Write lock tape drive" },
  { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
    &sim_tape_set_fmt, &sim_tape_show_fmt, NULL, "Define tape format" },
  { MTAB_XTD|MTAB_VUN, 0, "CAPACITY", "CAPACITY",
    &sim_tape_set_capac, &sim_tape_show_capac, NULL, "Specify tape capacity" },
  { MTAB_XTD|MTAB_VUN, 0, "TRANSPORT", NULL,
    NULL, &mt_show_transport, NULL, "Display type of tape transport" },
  { MTAB_XTD|MTAB_VUN, 0, NULL, "9TRACK",
    &mt_set_9track, NULL, NULL, "Set drive as 9-track transport" },
  { MTAB_XTD|MTAB_VUN, 0, NULL, "7TRACK",
    &mt_set_7track, NULL, NULL, "Set drive as 7-track transport" },
  { 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)" },
  { 0 }
};

/*
 * MT debug flags
 */
#define DBG_V_OPS       (DBG_SPECIFIC+0)/* Trace operations */
#define DBG_V_READ      (DBG_SPECIFIC+1)/* Dump read records */
#define DBG_V_RDATA     (DBG_SPECIFIC+2)/* Read through reg. 0 */
#define DBG_V_WDATA     (DBG_SPECIFIC+3)/* Write through reg. 0 */
#define DBG_V_MTIO      (DBG_SPECIFIC+4)/* Trace library routine calls */
#define DBG_V_DENS      (DBG_SPECIFIC+5)/* Trace density select changes */
#define DBG_V_SELECT    (DBG_SPECIFIC+6)/* Trace drive select/de-select */
#define DBG_V_RDSA      (DBG_SPECIFIC+7)/* Read data after DSA transfer */
#define DBG_V_WDSA      (DBG_SPECIFIC+8)/* Write data before DSA transfer */

#define DBG_OPS         (1 << DBG_V_OPS)
#define DBG_READ        (1 << DBG_V_READ)
#define DBG_RDATA       (1 << DBG_V_RDATA)
#define DBG_WDATA       (1 << DBG_V_WDATA)
#define DBG_MTIO        (1 << DBG_V_MTIO)
#define DBG_DENS        (1 << DBG_V_DENS)
#define DBG_SELECT      (1 << DBG_V_SELECT)
#define DBG_RDSA        (1 << DBG_V_RDSA)
#define DBG_WDSA        (1 << DBG_V_WDSA)

DEBTAB mt_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 of I/O instructions" },
  { "FIRSTREJ",    DBG_DFIRSTREJ,  "Suppress display of 2nd ... I/O rejects" },
  { "OPS",         DBG_OPS,        "Trace tape transport operations" },
  { "READ",        DBG_READ,       "Dump read records" },
  { "RDATA",       DBG_RDATA,      "Dump programmed I/O read data" },
  { "WDATA",       DBG_WDATA,      "Dump programmed I/O write data" },
  { "MTIO",        DBG_MTIO,       "Trace tape library routine calls" },
  { "DENS",        DBG_DENS,       "Trace denisty select changes" },
  { "SELECT",      DBG_SELECT,     "Trace transport select/de-select" },
  { "RDSA",        DBG_RDSA,       "Dump buffer after DSA read" },
  { "WDSA",        DBG_WDSA,       "Dump buffer before DSA write" },
  { NULL }
};

DEVICE mt_dev = {
  "MT", mt_unit, NULL, mt_mod,
  MT_NUMDR, 10, 31, 1, 8, 8,
  NULL, NULL, &mt_reset,
  &mt_boot, &mt_attach, &mt_detach,
  &MTdev,
  DEV_DEBUG | DEV_TAPE | DEV_DISABLE | DEV_INDEV | DEV_OUTDEV | DEV_PROTECT,
  0, mt_deb,
  NULL, NULL, &mt_help, NULL, NULL, NULL
};

/* MT trace routine */

void mt_trace(UNIT *uptr, const char *what, t_stat st, t_bool xfer)
{
  int32 u = uptr - mt_dev.units;
  const char *status = NULL;

  switch (st) {
    case MTSE_OK:
      status = "OK";
      break;

    case MTSE_TMK:
      status = "Tape Mark";
      break;

    case MTSE_UNATT:
      status = "Unattached";
      break;

    case MTSE_IOERR:
      status = "IO Error";
      break;

    case MTSE_INVRL:
      status = "Invalid Record Length";
      break;

    case MTSE_FMT:
      status = "Invalid Format";
      break;

    case MTSE_BOT:
      status = "Beginning Of Tape";
      break;

    case MTSE_EOM:
      status = "End Of Medium";
      break;

    case MTSE_RECE:
      status = "Error In Record";
      break;

    case MTSE_WRP:
      status = "Write Protected";
      break;

    case MTSE_LEOT:
      status = "Logical end of tape";
      break;

    case MTSE_RUNAWAY:
      status = "Tape runaway";
      break;
  }

  if (status != NULL) {
    if (xfer)
      fprintf(DBGOUT, "MT%d: %s, bytes %d - %s\r\n", u, what, MTremain, status)\
        ;
    else fprintf(DBGOUT, "MT%d: %s - %s\r\n", u, what, status);
  } else fprintf(DBGOUT, "MT%d: %s\r\n", u, what);
  if ((mt_dev.dctrl & DBG_DLOC) != 0)
    fprintf(DBGOUT, "MT%d: Inst: %llu\r\n", u, Instructions);
}

/* MT trace routine (DSA mode) */

void mt_DSAtrace(UNIT *uptr, const char *what)
{
  int32 u = uptr - mt_dev.units;

  fprintf(DBGOUT, "MT%d: DSA %s - CWA: 0x%04X, LWA: 0x%04X\r\n",
          u, what, MTdev.iod_CWA, MTdev.iod_LWA);
}

/* Tape library routine trace */

void mtio_trace(UNIT *uptr, const char *what, t_stat st, t_bool lvalid, t_mtrlnt len)
{
  int32 u = uptr - mt_dev.units;
  t_bool bot = FALSE, eot = FALSE;
  const char *status = "Unknown";

  if (st != MTSE_UNATT) {
    bot = sim_tape_bot(uptr);
    eot = sim_tape_eot(uptr);
  }

  switch (st) {
    case MTSE_OK:
      status = "OK";
      break;

    case MTSE_TMK:
      status = "Tape mark";
      break;

    case MTSE_UNATT:
      status = "Unattached";
      break;

    case MTSE_IOERR:
      status = "IO error";
      break;

    case MTSE_INVRL:
      status = "Invalid record length";
      break;

    case MTSE_FMT:
      status = "Invalid format";
      break;

    case MTSE_BOT:
      status = "Beginning of tape";
      break;

    case MTSE_EOM:
      status = "End of medium";
      break;

    case MTSE_RECE:
      status = "Error in record";
      break;

    case MTSE_WRP:
      status = "Write protected";
      break;

    case MTSE_LEOT:
      status = "Logical end of tape";
      break;

    case MTSE_RUNAWAY:
      status = "Tape runaway";
      break;
  }
  fprintf(DBGOUT, "MT%d: MTIO [%s %s] %s - %s\r\n",
          u, bot ? "BOT" : "", eot ? "EOT" : "", what, status);
  if (lvalid)
    fprintf(DBGOUT,
            "MT%d: MTIO Record len: %u, Mode: 0x%04X\r\n",
            u, len, MTdev.iod_mode);
}


/* Dump MT buffer */

char chars[128] = {
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  ' ', '!', '"', '#', '$', '%', '&', '\'',
  '(', ')', '*', '+', ',', '-', '.', '/',
  '0', '1', '2', '3', '4', '5', '6', '7',
  '8', '9', ':', ';', '<', '=', '>', '?',
  '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
  'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
  'X', 'Y', 'Z', '[', '\\', '|', '^', '_',
  ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
  'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
  'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
  'x', 'y', 'z', '{', ' ', '}', '~', ' '
};

void mt_dump(void)
{
  t_mtrlnt offset = 0, count = MTremain;
  char msg[80], text[16];

  if (MTremain > 0) {
    fprintf(DBGOUT, "Dump of MTbuf:\r\n");

    while (count > 0) {
      t_mtrlnt remain = count >= 10 ? 10 : count;
      t_mtrlnt i;

      msg[0] = '\0';

      for (i = 0; i < remain; i++) {
        char temp[8];

        text[i] = chars[MTbuf[offset] & 0x7F];

        sprintf(temp, "0x%02x", MTbuf[offset++]);
        if (msg[0] != '\0')
          strcat(msg, " ");
        strcat(msg, temp);
      }
      text[remain] = '\0';

      fprintf(DBGOUT, "%-55s%s\r\n", msg, text);

      count -= remain;
    }
  }
}

void mt_DSAdump(uint16 lwa, t_bool rw)
{
  uint16 cwa = MTdev.iod_FWA;
  int idx;
  char msg[80], text[16], temp[8];

  fprintf(DBGOUT, "Dump of DSA %s buffer (FWA: %04X, LWA: %04X):\r\n",
          rw ? "write" : "read", cwa, lwa);

  msg[0] = '\0';
  idx = 0;

  while (cwa != lwa) {
    text[idx++] = chars[(M[cwa] >> 8) & 0x7F];
    text[idx++] = chars[M[cwa] & 0x7F];

    sprintf(temp, "0x%04X", M[cwa]);
    if (msg[0] != '\0')
      strcat(msg, " ");
    strcat(msg, temp);

    if (idx == 10) {
      text[idx++] = '\0';
      fprintf(DBGOUT, "%-55s%s\r\n", msg, text);
      msg[0] = '\0';
      idx = 0;
    }
    cwa++;
  }

  if (idx != 0) {
    text[idx++] = '\0';
    fprintf(DBGOUT, "%-55s%s\r\n", msg, text);
  }
}

/*
 * Dump the current internal state of the MT device.
 */
const char *MTstateStr[] = {
  "Idle", "Reading", "Writing", "Read Timeout", "Write Timeout", "DSA Done"
};

void MTstate(const char *where, DEVICE *dev, IO_DEVICE *iod)
{
  char device[16];

  strcpy(device, "None");
  if (iod->iod_unit != NULL) {
    int32 u = iod->iod_unit - dev->units;

    sprintf(device, "MT%u", u);
  }

  fprintf(DBGOUT,
          "%s[%s %s: Func: %04X, Sta: %04X, Sta2: %04X, Ena: %04X]\r\n",
          INTprefix, dev->name, where,
          iod->FUNCTION, iod->STATUS, iod->STATUS2, iod->IENABLE);
  fprintf(DBGOUT,
          "%s[%s %s: Sel: %s, %s%s]\r\n",
          INTprefix, dev->name, where, device, MTstateStr[MTmode],
          iod->iod_wasWriting ? ", Was writing" : "");
}

void mt_data(UNIT *uptr, t_bool output, uint16 data)
{
  int32 u = uptr - mt_dev.units;

  fprintf(DBGOUT, "MT%d: %s - 0x%04x\r\n", u, output ? "wrote" : "read", data);
}

t_stat mt_show_type(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
  switch (MTdev.iod_type) {
    case DEVTYPE_1732_A:
      fprintf(st, "1732-A Magnetic Tape Controller");
      break;

    case DEVTYPE_1732_3:
      fprintf(st, "1732-3 Magnetic Tape Controller");
      break;

    default:
      return SCPE_IERR;
  }
  return SCPE_OK;
}

t_stat mt_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
  if (!cptr)
    return SCPE_IERR;
  if ((uptr->flags & UNIT_ATT) != 0)
    return SCPE_ALATT;

  if (!strcmp(cptr, "1732-A")) {
    MTdev.iod_type = DEVTYPE_1732_A;
    MTdev.iod_model = "1732-A";
    MTdev.iod_flags &= ~AQ_ONLY;
    mt_dev.registers = mt_reg_1732_A;
    buildDCtables();
  } else {
    if (!strcmp(cptr, "1732-3")) {
      MTdev.iod_type = DEVTYPE_1732_3;
      MTdev.iod_model = "1732-3";
      MTdev.iod_flags |= AQ_ONLY;
      mt_dev.registers = mt_reg_1732_3;
      buildDCtables();
    } else return SCPE_ARG;
  }
  return SCPE_OK;
}

/*
 * Display magtape transport
 */
t_stat mt_show_transport(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
  if (uptr == NULL)
    return SCPE_IERR;

  if (MTdev.iod_type == DEVTYPE_1732_A) {
    if ((uptr->flags & UNIT_7TRACK) != 0)
      fprintf(st, "7-track 608 transport");
    else fprintf(st, "9-track 609 transport");
  } else {
    if ((uptr->flags & UNIT_7TRACK) != 0)
      fprintf(st, "7-track 6173 transport");
    else fprintf(st, "9-track 6193 transport");
  }
  return SCPE_OK;
}

/*
 * Set drive to 9-track transport.
 */
t_stat mt_set_9track(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
  if (uptr == NULL)
    return SCPE_IERR;

  if ((uptr->flags & UNIT_ATT) != 0)
    return SCPE_ALATT;

  uptr->flags &= ~UNIT_7TRACK;
  return SCPE_OK;
}

/*
 * Set drive to 7-track transport.
 */
t_stat mt_set_7track(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
  if (uptr == NULL)
    return SCPE_IERR;

  if ((uptr->flags & UNIT_ATT) != 0)
    return SCPE_ALATT;

  uptr->flags |= UNIT_7TRACK;
  return SCPE_OK;
}

/*
 * Compute the delay time between new data being available from tape. This
 * will be dependent on the density of the tape and the speed of the drive
 * (in this case we assume 37.5 inches per sec).
 */
int32 mt_densityTimeout(t_bool loose)
{
  int32 result = MT_200_WAIT;

  switch (MTdev.STATUS2 & (IO_ST2_556 | IO_ST2_800)) {
    case 0:
      result = MT_200_WAIT;
      break;

    case IO_ST2_556:
      result = MT_556_WAIT;
      break;

    case IO_ST2_800:
      result = MT_800_WAIT;
      break;
  }

  if (MTdev.iod_type == DEVTYPE_1732_3)
    if ((MTdev.STATUS2 & IO_ST2_1600) != 0)
      result = MT_1600_WAIT;

  if ((MTdev.iod_mode & IO_1732_ASSEM) != 0)
    result *= 2;

  return loose ? LOOSETIMING(result) : result;
}

/* Unit service */

t_stat mt_svc(UNIT *uptr)
{
  uint16 mask = IO_1732_STMSK;
  uint16 delay = MTdev.iod_delay;
  uint16 result;
  t_stat status;
  t_mtrlnt temp;
  int32 tmo;

  if ((mt_dev.dctrl & DBG_OPS) != 0)
    mt_trace(uptr, "mt_svc", (t_stat)-1, FALSE);

  MTdev.iod_delay = 0;

  /*
   * Check for local (internal) delays.
   */
  if ((delay & IO_LOCAL_MASK) != 0) {
    switch (delay) {
      case IO_DELAY_RDATA:
        fw_IOintr(FALSE, &mt_dev, &MTdev, IO_ST_DATA, 0, 0xFFFF, "Read Ready");
        tmo = mt_densityTimeout(TRUE);
        MTdev.iod_event = Instructions + tmo;

        MTdev.iod_delay = IO_DELAY_RTMO;
        sim_activate(uptr, tmo);

        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "Delayed IO_ST_DATA for read", (t_stat)-1, FALSE);
        break;

      case IO_DELAY_WDATA:
        fw_IOintr(FALSE, &mt_dev, &MTdev, IO_ST_DATA, 0, 0xFFFF, "Write Ready");
        tmo = mt_densityTimeout(TRUE);
        MTdev.iod_event = Instructions + tmo;

        MTdev.iod_delay = IO_DELAY_WTMO;
        sim_activate(uptr, tmo);

        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "Delayed IO_ST_DATA for write", (t_stat)-1, FALSE);
        break;

      case IO_DELAY_RTMO:
        MTmode = MT_READTMO;

        /*
         * Drop DATA and schedule EOP completion
         */
        MTdev.STATUS &= ~IO_ST_DATA;
        MTdev.iod_delay = IO_DELAY_EOP;

        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "Read buffer timed out", (t_stat)-1, FALSE);

        if (MTremain != 0) {
          MTdev.STATUS |= IO_ST_ALARM | IO_ST_LOST;
          MTdev.iod_reason = "Read timed out - data lost";
          MTdev.iod_oldienable = MTdev.iod_ienable;
          MTdev.iod_ienable &= ~IO_DIR_DATA;
        } else MTdev.iod_reason = "Read timed out";

        MTremain = 0;
        sim_activate(uptr, MT_EOP_WAIT);
        break;

      case IO_DELAY_WTMO:
        MTmode = MT_WRITETMO;
        status = sim_tape_wrrecf(uptr, MTbuf, MToffset);

        if ((mt_dev.dctrl & DBG_MTIO) != 0)
          mtio_trace(uptr, "wrrecf", status, TRUE, MToffset);

        /*
         * Drop DATA and schedule EOP completion
         */
        MTdev.STATUS &= ~IO_ST_DATA;
        MTdev.iod_delay = IO_DELAY_EOP;
        MTdev.iod_reason = "Write timed out";
        sim_activate(uptr, MT_EOP_WAIT);

        if ((mt_dev.dctrl & DBG_OPS) != 0) {
          MTremain = MToffset;
          mt_trace(uptr, "Write buffer timed out", status, TRUE);
        }
        break;

      case IO_DELAY_EOP:
        MTmode = MT_IDLE;
        fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, ~IO_1732_ACTIVE, MTdev.iod_reason);
        MTdev.iod_reason = NULL;

        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "Delayed EOP", (t_stat)-1, FALSE);
        break;

      case IO_DSA_READ:
        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_DSAtrace(uptr, "read");

        if (MTdev.iod_CWA == MTdev.iod_LWA) {
          /*
           * DSA read transfer complete.
           */
          MTmode = MT_DSADONE;
          MTdev.iod_delay = IO_DELAY_EOP;
          MTdev.iod_reason = "DSA read complete";
          sim_activate(uptr, MT_EOP_WAIT);

          if ((mt_dev.dctrl & DBG_OPS) != 0)
            mt_trace(uptr, "DSA read complete", (t_stat)-1, FALSE);
          if ((mt_dev.dctrl & DBG_RDSA) != 0)
            mt_DSAdump(MTdev.iod_LWA, FALSE);
          break;
        }

        /*
         * If there is no data available, terminate the transfer
         */
        if (MTremain == 0) {
          /*
           * DSA read complete - no more data.
           */
          MTmode = MT_DSADONE;
          MTdev.iod_delay = IO_DELAY_EOP;
          MTdev.iod_reason = "DSA read complete - no data";
          sim_activate(uptr, MT_EOP_WAIT);

          if ((mt_dev.dctrl & DBG_OPS) != 0)
            mt_trace(uptr, "DSA read complete - no data", (t_stat)-1, FALSE);
          if ((mt_dev.dctrl & DBG_RDSA) != 0)
            mt_DSAdump(MTdev.iod_CWA, FALSE);
          break;
        }

        if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) {
          if (MTremain >= 2) {
            result = (MTbuf[MToffset] << 8) | MTbuf[MToffset + 1];
            MToffset += 2;
            MTremain -= 2;
          } else {
            MTdev.STATUS |= IO_1732_FILL;
            result = MTbuf[MToffset] << 8;
            MToffset++;
            MTremain--;
          }
        } else {
          result = MTbuf[MToffset];
          MToffset++;
          MTremain--;
        }

        if ((uptr->flags & UNIT_7TRACK) != 0)
          result &= 0x3F3F;

        if (!IOStoreToMem(MTdev.iod_CWA, result, TRUE)) {
          /*** TODO: generate device protect error ***/
        }
        MTdev.iod_CWA++;
        MTdev.iod_delay = IO_DSA_READ;
        sim_activate(uptr, mt_densityTimeout(FALSE));
        break;

      case IO_DSA_WRITE:
        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_DSAtrace(uptr, "write");

        if (MTdev.iod_CWA == MTdev.iod_LWA) {
          /*
           * DSA write transfer complete.
           */
          status = sim_tape_wrrecf(uptr, MTbuf, MToffset);

          if ((mt_dev.dctrl & DBG_MTIO) != 0)
            mtio_trace(uptr, "wrrecf", status, TRUE, MToffset);

          MTmode = MT_DSADONE;
          MTdev.iod_delay = IO_DELAY_EOP;
          MTdev.iod_reason = "DSA write complete";
          sim_activate(uptr, MT_EOP_WAIT);

          if ((mt_dev.dctrl & DBG_OPS) != 0)
            mt_trace(uptr, "DSA write complete", (t_stat)-1, FALSE);
          break;
        }

        result = LoadFromMem(MTdev.iod_CWA);

        if ((uptr->flags & UNIT_7TRACK) != 0)
          result &= 0x3F3F;

        MTdev.iod_CWA++;

        if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) {
          MTbuf[MToffset] = (result >> 8) & 0xFF;
          MTbuf[MToffset + 1] = result & 0xFF;
          MToffset += 2;
        } else {
          MTbuf[MToffset] = result & 0xFF;
          MToffset += 1;
        }

        MTdev.iod_delay = IO_DSA_WRITE;
        sim_activate(uptr, mt_densityTimeout(FALSE));
        break;
    }
    return SCPE_OK;
  }

  /*
   * Check if we need to write a tape mark before processing the request.
   */
  if (MTdev.iod_wasWriting)
    switch (delay) {
      case IO_1732_BACKSP:
      case IO_1732_REWL:
      case IO_1732A_REWU:
        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "Forced TM (BACKSP, REWL, REWU)", (t_stat)-1, FALSE);
        status = sim_tape_wrtmk(uptr);

        if ((mt_dev.dctrl & DBG_MTIO) != 0)
          mtio_trace(uptr, "wrtmk", status, FALSE, 0);
        break;
    }

  /*
   * Command specific processing
   */
  switch (delay) {
    /*
     * The following commands normally do not set "end of operation". "read
     * motion" does set "end of operation" if a tape mark or end of tape
     * is detected.
     */
    case IO_1732_READ:
      MTremain = 0;
      status = sim_tape_rdrecf(uptr, MTbuf, &MTremain, MTSIZ);

      if ((mt_dev.dctrl & DBG_MTIO) != 0)
        mtio_trace(uptr, "rdrecf", status, TRUE, MTremain);

      switch (status) {
        case MTSE_OK:
          break;

        case MTSE_TMK:
          MTdev.STATUS |= IO_ST_ALARM | IO_1732_FMARK;
          break;

        case MTSE_EOM:
          MTdev.STATUS |= IO_ST_ALARM | IO_1732_EOT;
          break;

        case MTSE_RECE:
         MTdev.STATUS |= IO_ST_ALARM | IO_ST_PARITY;
         MTremain = 0;
         break;
      }
      MToffset = 0;

      if ((MTdev.STATUS & (IO_1732_FMARK | IO_1732_EOT | IO_ST_PARITY)) == 0)
        mask &= ~IO_ST_EOP;

      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "READ", status, TRUE);
      if ((mt_dev.dctrl & DBG_READ) != 0)
        mt_dump();

      if (MTremain > 0) {
        if (MTdev.iod_DSApending) {
          MTdev.iod_DSApending = FALSE;
          MTdev.iod_delay = IO_DSA_READ;
          sim_activate(uptr, mt_densityTimeout(FALSE));
          if ((mt_dev.dctrl & DBG_OPS) != 0) {
            int32 u = uptr - mt_dev.units;
            
            fprintf(DBGOUT,
                    "[MT%d: DSA Read started, CWA: 0x%04X, LWA: 0x%04X, Mode: 0x%X\r\n",
                    u, MTdev.iod_CWA, MTdev.iod_LWA, MTdev.iod_mode);
          }
          return SCPE_OK;
        }
        MTdev.iod_delay = IO_DELAY_RDATA;
        sim_activate(uptr, MT_MIN_WAIT);
        return SCPE_OK;
      }
      MTmode = MT_IDLE;
      break;

    case IO_1732_WRITE:
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "WRITE", (t_stat)-1, FALSE);

      if (MTdev.iod_DSApending) {
        MTdev.iod_DSApending = FALSE;
        MTdev.iod_delay = IO_DSA_WRITE;

        if ((mt_dev.dctrl & DBG_WDSA) != 0)
          mt_DSAdump(MTdev.iod_LWA, TRUE);

        sim_activate(uptr, mt_densityTimeout(FALSE));
        if ((mt_dev.dctrl & DBG_OPS) != 0) {
          int32 u = uptr - mt_dev.units;

          fprintf(DBGOUT,
                  "[MT%d: DSA Write started, CWA: 0x%04X, LWA: 0x%04X, Mode: 0x%X\r\n",
                  u, MTdev.iod_CWA, MTdev.iod_LWA, MTdev.iod_mode);
        }
        return SCPE_OK;
      }
      MTdev.iod_delay = IO_DELAY_WDATA;
      sim_activate(uptr, MT_MIN_WAIT);
      return SCPE_OK;

    case IO_1732A_REWU:
      status = sim_tape_rewind(uptr);

      if ((mt_dev.dctrl & DBG_MTIO) != 0)
        mtio_trace(uptr, "rewind & unload", status, FALSE, 0);

      MTdev.STATUS |= IO_1732_BOT;
      mt_detach(uptr);
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "REWU", status, FALSE);
      
      mask &= ~IO_ST_EOP;
      break;

    /*
     * The following commands set "end of operation" when the command
     * completes.
     */
    case IO_1732_BACKSP:
      status = sim_tape_sprecr(uptr, &temp);

      if ((mt_dev.dctrl & DBG_MTIO) != 0)
        mtio_trace(uptr, "sprecr", status, FALSE, 0);

      if (status == MTSE_TMK)
        MTdev.STATUS |= IO_1732_FMARK;
      if (sim_tape_bot(uptr))
        MTdev.STATUS |= IO_1732_BOT;
      if (sim_tape_eot(uptr))
        MTdev.STATUS |= IO_1732_EOT;
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "BACKSP", status, FALSE);
      break;

    case IO_1732_WFM:
      status = sim_tape_wrtmk(uptr);

      if ((mt_dev.dctrl & DBG_MTIO) != 0)
        mtio_trace(uptr, "wrtmk", status, FALSE, 0);

#if 0
      MTdev.STATUS |= IO_ST_ALARM | IO_1732_FMARK;
#endif
      if (sim_tape_eot(uptr))
        MTdev.STATUS |= IO_1732_EOT;
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "WFM", status, FALSE);
      break;

    case IO_1732_SFWD:
      status = MTSE_OK;
      while (!sim_tape_eot(uptr)) {
        status = sim_tape_sprecf(uptr, &temp);
        
        if ((mt_dev.dctrl & DBG_MTIO) != 0)
          mtio_trace(uptr, "sprecf", status, FALSE, 0);
        
        if (status == MTSE_TMK)
          MTdev.STATUS |= IO_1732_FMARK;

        if (status != MTSE_OK)
          break;
      }
      if (sim_tape_bot(uptr))
        MTdev.STATUS |= IO_1732_BOT;
      if (sim_tape_eot(uptr))
        MTdev.STATUS |= IO_1732_EOT;
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "SFWD", status, FALSE);
      break;

     case IO_1732_SBACK:
       status = MTSE_OK;
       while (!sim_tape_bot(uptr)) {
         status = sim_tape_sprecr(uptr, &temp);

         if ((mt_dev.dctrl & DBG_MTIO) != 0)
           mtio_trace(uptr, "sprecr", status, FALSE, 0);

         if (status == MTSE_TMK)
           MTdev.STATUS |= IO_1732_FMARK;

         if (status != MTSE_OK)
           break;
       }
       if (sim_tape_bot(uptr))
         MTdev.STATUS |= IO_1732_BOT;
       if (sim_tape_eot(uptr))
         MTdev.STATUS |= IO_1732_EOT;
       if ((mt_dev.dctrl & DBG_OPS) != 0)
         mt_trace(uptr, "SBACK", status, FALSE);
       break;

    case IO_1732_REWL:
      status = sim_tape_rewind(uptr);

      if ((mt_dev.dctrl & DBG_MTIO) != 0)
        mtio_trace(uptr, "rewind", status, FALSE, 0);
      
      MTdev.STATUS |= IO_1732_BOT;
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_trace(uptr, "REWL", status, FALSE);
      break;
  }

  /*
   * If we are at a tape mark or end of tape, no data is available.
   */
  if ((MTdev.STATUS & (IO_1732_FMARK | IO_1732_EOT)) != 0)
    mask &= ~IO_ST_DATA;

  /*
   * Controller is no longer active.
   */
  mask &= ~IO_1732_ACTIVE;

  /*
   * I/O is now complete.
   */
  fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, mask, "Operation Complete");
  return SCPE_OK;
}

/* Reset routine */

t_stat mt_reset(DEVICE *dptr)
{
  t_stat r;

  if (MTdev.iod_type == IOtype_default) {
    /*
     * Setup the default device type.
     */
    MTdev.iod_type = DEVTYPE_1732_A;
    MTdev.iod_model = "1732-A";
    MTdev.iod_flags &= ~AQ_ONLY;
    mt_dev.registers = mt_reg_1732_A;
    buildDCtables();
  }

  if (IOFWinitialized)
    if ((dptr->flags & DEV_DIS) == 0)
      if ((r = checkReset(dptr, MTdev.iod_equip)) != SCPE_OK)
        return r;

  DEVRESET(&MTdev);

  MTdev.STATUS = 0;
  MTdev.STATUS2 = 0;

  MTdev.iod_mode = 0;
  MTdev.iod_unit = NULL;
  MTdev.iod_delay = 0;
  MTdev.iod_wasWriting = FALSE;
  MTdev.iod_CWA = MTdev.iod_LWA = 0;
  MTdev.iod_DSApending = FALSE;
  MTmode = MT_IDLE;

  return SCPE_OK;
}

/* Boot routine */

t_stat mt_boot(int32 unitno, DEVICE *dptr)
{
  if (unitno != 0) {
    sim_printf("Can only boot from drive 0\n");
    return SCPE_ARG;
  }

  if ((sim_switches & SWMASK('S')) != 0) {
    /*
     * Special bootstrap for System Maintenance Monitor (SMM17)
     */
    uint16 base, equip;

    base = ((cpu_unit.capac - 1) & 0xF000) | 0xFE0;
    loadBootstrap(smm17boot9, SMM17BOOTLEN9, base, base);

    /*
     * Compute the equipment address to use and patch it into memory.
     */
    equip = (MTdev.iod_equip << 7) | 2;
    if ((sim_switches & SWMASK('D')) != 0)
      equip |= 0x1000;

    M[base + 2] = equip;

    return SCPE_OK;
  }

  loadBootstrap(mtbootstrap9, MTBOOTLEN9, 0, 0);

  /*
   * Set A register according to the amount of memory installed.
   */
  Areg = 0x5000;
  if (cpu_unit.capac < 32768)
    Areg = 0x4000;
  if (cpu_unit.capac < 24576)
    Areg = 0x2000;

  return SCPE_OK;
}

/* Attach routine */

t_stat mt_attach(UNIT *uptr, CONST char *cptr)
{
  t_stat r;

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

  uptr->flags &= ~UNIT_WPRT;
  if (sim_switches & SWMASK('R'))
    uptr->flags |= UNIT_WPRT;

  uptr->DENS = IO_ST2_800;

  /*
   * If this units is currently selected, make it accessible.
   */
  if (MTdev.iod_unit == uptr) {
    MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800);
    if ((uptr->flags & UNIT_WPRT) != 0)
      MTdev.STATUS2 &= ~IO_ST2_WENABLE;
    else MTdev.STATUS2 |= IO_ST2_WENABLE;
    if ((uptr->flags & UNIT_7TRACK) != 0)
      MTdev.STATUS2 |= IO_ST2_7TRACK;
    else MTdev.STATUS2 &= ~IO_ST2_7TRACK;
    fw_setForced(&MTdev, IO_ST_READY);
  }
  return r;
}

/* Detach routine */

t_stat mt_detach(UNIT *uptr)
{
  t_stat st;

  if ((uptr->flags & UNIT_ATT) == 0)
    return SCPE_OK;

  sim_cancel(uptr);

  st = sim_tape_detach(uptr);
  if (st == MTSE_OK) {
    if (MTdev.iod_unit == uptr)
      fw_clearForced(&MTdev, IO_ST_READY);
  }
  return st;
}

/*
 * Perform a "Clear Controller" operation. Basically this is similar to a
 * device reset except it does not forget the currently selected transport.
 */
void MTclear(DEVICE *dptr)
{
  UNIT *uptr;

  DEVRESET(&MTdev);

  MTdev.STATUS = 0;
  MTdev.STATUS2 = 0;

  MTdev.iod_mode = 0;
  MTdev.iod_delay = 0;
  MTdev.iod_wasWriting = FALSE;
  MTmode = MT_IDLE;

  if ((uptr = MTdev.iod_unit) != NULL) {
    fw_setForced(&MTdev, IO_ST_READY);

    MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800);
    if ((uptr->flags & UNIT_WPRT) != 0)
      MTdev.STATUS2 &= ~IO_ST2_WENABLE;
    else MTdev.STATUS2 |= IO_ST2_WENABLE;
    if ((uptr->flags & UNIT_7TRACK) != 0)
      MTdev.STATUS2 |= IO_ST2_7TRACK;
    else MTdev.STATUS2 &= ~IO_ST2_7TRACK;
  }
}

/*
 * Check if I/O should be rejected. I/O allowed if:
 *
 * Reg.                 Write (OUT)                     Read (INP)
 *
 *  00          Ready and writing active        Ready and data available
 *  01          Controller dependent            Always allowed
 *              Always allow Clear Interrupts/Controller
 *              1732-A: Reject if controller active
 *              1732-3: Always allow
 *  10          Controller active clear         Always allowed
 *  11          Controller busy and EOP clear   Always allowed
 *
 * If a data I/O (register 0) is performed after the tape motion has timed
 * out, we need to generate an ALARM + LOST data status.
 */
t_bool MTreject(IO_DEVICE *iod, t_bool output, uint8 reg)
{
  switch (reg) {
    case 0:
      if (output)
        return ((iod->STATUS & (IO_ST_DATA | IO_ST_READY)) !=
                (IO_ST_DATA | IO_ST_READY)) || (MTmode != MT_WRITING);

      return ((iod->STATUS & (IO_ST_DATA | IO_ST_READY)) !=
              (IO_ST_DATA | IO_ST_READY)) || (MTremain == 0);

    case 1:
      if (output) {
        if (MTdev.iod_type == DEVTYPE_1732_A)
          if ((IOAreg & (IO_DIR_CCONT | IO_DIR_CINT)) == 0)
            return ((iod->STATUS & IO_1732_ACTIVE) != 0);
      }
      break;
      
    case 2:
      if (output)
        return ((iod->STATUS & IO_1732_ACTIVE) != 0);
      break;

    case 3:
      if (MTdev.iod_type != DEVTYPE_1732_3)
        return TRUE;
      if (output)
        return ((iod->STATUS & (IO_ST_EOP | IO_ST_BUSY)) == IO_ST_BUSY);
      break;
  }
  return FALSE;
}

/* Perform an input operation on a selected drive. This can be performed
   by issuing a command directly to the device or via a 1706 */

enum IOstatus doMTIn(UNIT *uptr, uint16 *data, t_bool via1706)
{
  uint16 result;

  /*
   * Reject the request if we are not reading or data is not available
   */
  if ((MTmode != MT_READING) || ((MTdev.STATUS & IO_ST_DATA) == 0))
    return IO_REJECT;

  sim_cancel(uptr);

  if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) {
    if (MTremain >= 2) {
      result = (MTbuf[MToffset] << 8) | MTbuf[MToffset + 1];
      MToffset += 2;
      MTremain -= 2;
    } else {
      MTdev.STATUS |= IO_1732_FILL;
      result = MTbuf[MToffset] << 8;
      MToffset++;
      MTremain--;
    }
  } else {
    result = MTbuf[MToffset];
    MToffset++;
    MTremain--;
  }
  if ((uptr->flags & UNIT_7TRACK) != 0)
    result &= 0x3F3F;

  if ((mt_dev.dctrl & DBG_RDATA) != 0)
    mt_data(uptr, FALSE, result);

  fw_IOintr(FALSE, &mt_dev, &MTdev, 0, IO_ST_DATA, 0xFFFF, NULL);

  if (MTremain != 0) {
    MTdev.iod_delay = IO_DELAY_RDATA;
    sim_activate(uptr, (int32)(MTdev.iod_event - Instructions));
  } else {
    MTmode = MT_IDLE;
    MTdev.STATUS |= IO_ST_EOP;
    MTdev.STATUS &= ~(IO_1732_ACTIVE | IO_ST_BUSY);
    if ((mt_dev.dctrl & DBG_OPS) != 0)
      mt_trace(uptr, "Consumed read buffer", (t_stat)-1, FALSE);
  }

  *data = result;
  return IO_REPLY;
}

/* Perform an output operation on a selected drive. This can be performed
   by issuing a command directly to the device or via a 1706 */

enum IOstatus doMTOut(UNIT *uptr, uint16 *data, t_bool via1706)
{
  uint16 temp = *data;
  t_mtrlnt need = ((MTdev.iod_mode & IO_1732_ASSEM) != 0) ? 2 : 1;

  /*
   * Reject the request if we are not writing or data cannot be written.
   */
  if ((MTmode != MT_WRITING) || (MTdev.STATUS & IO_ST_DATA) == 0)
    return IO_REJECT;

  sim_cancel(uptr);

  if ((uptr->flags & UNIT_7TRACK) != 0)
    temp &= 0x3F3F;

  if (MTremain < need)
    return IO_REJECT;

  if ((MTdev.iod_mode & IO_1732_ASSEM) != 0) {
    MTbuf[MToffset] = (temp >> 8) & 0xFF;
    MTbuf[MToffset + 1] = temp & 0xFF;
    MToffset += 2;
    MTremain -= 2;
  } else {
    MTbuf[MToffset] = temp & 0xFF;
    MToffset += 1;
    MTremain -= 1;
  }

  if ((mt_dev.dctrl & DBG_WDATA) != 0)
    mt_data(uptr, TRUE, temp);

  fw_IOintr(FALSE, &mt_dev, &MTdev, 0, IO_ST_DATA, 0xFFFF, NULL);
  MTdev.iod_delay = IO_DELAY_WDATA;
  sim_activate(uptr, (int32)(MTdev.iod_event - Instructions));

  return IO_REPLY;
}

/* Perform control function */

enum IOstatus doMTFunction(DEVICE *dev)
{
  UNIT *uptr;
  t_stat st;

  /*
   * Handle commands in the following order:
   *
   * 1. Handle clears
   * 2. Handle interrupt selections
   * 3. Handle motion control
   */
  switch (IOAreg & IO_1732_MOTION) {
    case 0:
    case IO_1732_WRITE:
    case IO_1732_READ:
    case IO_1732_BACKSP:
    case IO_1732_WFM:
    case IO_1732_SFWD:
    case IO_1732_SBACK:
    case IO_1732_REWL:
      break;

    case IO_1732A_REWU:
      if (MTdev.iod_type == DEVTYPE_1732_3)
        return IO_REJECT;
      break;

    default:
      return IO_REJECT;
  }

  if (doDirectorFunc(&mt_dev, TRUE)) {
    /*
     * The device interrupt mask has been explicitly changed. If the device
     * state is such that an interrupt can occur, generate it now.
     */
    fw_IOintr(FALSE, &mt_dev, &MTdev, 0, 0, 0xFFFF, "Mask change interrupt");
  }

  /*
   * All done if there is no motion control requested.
   */
  if ((IOAreg & IO_1732_MOTION) == 0)
    return IO_REPLY;

  /*
   * Drive must be selected to perform a motion operation
   */
  if ((uptr = MTdev.iod_unit) == NULL)
    return IO_REJECT;

  /*
   * We now know we have a valid motion command.
   */
  MTdev.iod_wasWriting = MTmode == MT_WRITING;

  /*
   * If we are currently writing to the tape, terminate the current
   * record before initiating the new tape motion command.
   */
  if (MTmode == MT_WRITING) {
    st = sim_tape_wrrecf(uptr, MTbuf, MToffset);

    if ((mt_dev.dctrl & DBG_MTIO) != 0)
      mtio_trace(uptr, "wrrecf", st, TRUE, MToffset);

    MTmode = MT_IDLE;
    MTdev.STATUS &= ~IO_1732_ACTIVE;
  }

  /*
   * Clear ALARM, LOST data, FILL and any position information on a motion
   * operation
   */
  if ((IOAreg & IO_1732_MOTION) != 0) {
    MTdev.STATUS &= ~IO_ST_ALARM;
    MTdev.STATUS &= ~(IO_ST_LOST | IO_1732_FILL);
    MTdev.STATUS &= ~(IO_1732_FMARK | IO_1732_EOT | IO_1732_BOT);
  }

  switch (IOAreg & IO_1732_MOTION) {
    case IO_1732_READ:
      MTmode = MT_READING;
      goto active;

    case IO_1732_WRITE:
      MTmode = MT_WRITING;
      MToffset = 0;
      MTremain = MTSIZ;
      goto active;

    case IO_1732_BACKSP:
    case IO_1732_WFM:
    case IO_1732_SFWD:
    case IO_1732_SBACK:
  active:
      MTdev.STATUS |= IO_1732_ACTIVE;
      break;

    case IO_1732_REWL:
      if (!MTdev.iod_wasWriting && sim_tape_bot(uptr)) {
        /*
         * If we are currently standing at the load point, complete the
         * request immediately. Diagnostic 0F (BD2) relies on this
         * behaviour.
         */
        MTdev.STATUS |= IO_1732_BOT;
        if ((mt_dev.dctrl & DBG_OPS) != 0)
          mt_trace(uptr, "REWL", (t_stat)-1, FALSE);

        fw_IOcompleteEOP(FALSE, &mt_dev, &MTdev, 0xFFFF, "Rewind complete");
        return IO_REPLY;
      }
      /* FALLTHROUGH */

    case IO_1732A_REWU:
      break;
  }

  /*
   * Mark I/O underway and activate a delayed operation.
   */
  fw_IOunderwayEOP(&MTdev, 0);

  sim_cancel(uptr);
  MTdev.iod_delay = Areg & IO_1732_MOTION;
  sim_activate(uptr, MT_MOTION_WAIT);
  return IO_REPLY;
}

/* Perform I/O */

enum IOstatus MTin(IO_DEVICE *iod, uint8 reg)
{
  UNIT *uptr = MTdev.iod_unit;

  /*
   * The framework only passes INP operations for the data register (0x380).
   */
  if (uptr != NULL) {
    if (((MTdev.STATUS & IO_ST_READY) != 0) && (MTremain != 0)) {
      return doMTIn(uptr, &Areg, FALSE);
    }
  }
  return IO_REJECT;
}

enum IOstatus MTout(IO_DEVICE *iod, uint8 reg)
{
  UNIT *uptr = MTdev.iod_unit;
  uint16 unit;

  switch (reg) {
    case 0x00:
      if (uptr != NULL) {
        if ((MTdev.STATUS & IO_ST_READY) != 0)
          return doMTOut(uptr, &Areg, FALSE);
      }
      return IO_REJECT;

    case 0x01:
      return doMTFunction(MTdev.iod_outdev);

    case 0x02:
      /*
       * Get the unit number for select
       */
      unit = MTdev.iod_type == DEVTYPE_1732_3 ? IO_1732_UNIT : IO_1732A_UNIT;
      unit = (unit & Areg) >> 7;

      /*
       * Check for invalid bit combinations.
       */
      if ((Areg & IO_1732_PARITY) == IO_1732_PARITY)
        return IO_REJECT;

      if ((Areg & IO_1732_DESEL) != 0)
        if ((Areg & ~IO_1732_DESEL) != 0)
          return IO_REJECT;

      if ((Areg & IO_1732_SEL) != 0) {
        /*
         * Check for illegal unit select.
         */
        if (unit >= mt_dev.numunits)
          return IO_REJECT;
      }

      switch (Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800)) {
        case IO_1732_1600:      /* IO_1732A_200 on 1732-A */
        case IO_1732_556:
        case IO_1732_800:
          if (uptr != NULL)
            if ((mt_dev.dctrl & DBG_DENS) != 0) {
              DEVICE *dptr = find_dev_from_unit(uptr);
              int32 u = uptr - dptr->units;
              
              fprintf(DBGOUT,
                      "MT%d: Density changed to %04X\r\n",
                      u, Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800));
            }
          /* FALLTHROUGH */

        case 0:                         /* No change in density */
          break;

        default:
          return IO_REJECT;
      }

      /*
       * Process the select/deselect operation.
       */
      if ((Areg & IO_1732_DESEL) != 0) {
        /*** TODO: Implement protected device support ***/
        if ((mt_dev.dctrl & DBG_SELECT) != 0)
          if (MTdev.iod_unit != NULL) {
            DEVICE *dptr = find_dev_from_unit(uptr);
            int32 u = uptr - dptr->units;
            
            fprintf(DBGOUT, "MT%d - Deselected\r\n", u);
          }

        MTdev.iod_unit = NULL;
        fw_clearForced(&MTdev, IO_ST_READY);
        MTdev.STATUS2 = 0;
        return IO_REPLY;
      }

      if ((Areg & IO_1732_SEL) != 0) {
        MTdev.iod_unit = NULL;
        MTdev.STATUS &= ~(IO_1732_STCINT | IO_1732_FMARK | IO_1732_EOT);
        fw_clearForced(&MTdev, IO_ST_READY);
        
        uptr = &mt_unit[unit];

        if ((uptr->flags & UNIT_ATT) != 0) {
          MTdev.iod_unit = uptr;
          fw_setForced(&MTdev, IO_ST_READY);

          if (sim_tape_bot(uptr))
            MTdev.STATUS |= IO_1732_BOT;
          if (sim_tape_eot(uptr))
            MTdev.STATUS |= IO_1732_EOT;
        }
        if ((mt_dev.dctrl & DBG_SELECT) != 0)
          fprintf(DBGOUT, "MT%d Selected\r\n", unit);

        MTdev.STATUS2 = 0;
      }

      /*
       * Remember the current mode of operation.
       */
      MTdev.iod_mode = Areg;

      if ((uptr = MTdev.iod_unit) != NULL) {
        /*
         * If this operation modifies the density, remember it for later.
         */
        if ((Areg & (IO_1732_1600 | IO_1732_556 | IO_1732_800)) != 0) {
          if ((uptr->flags & UNIT_7TRACK) != 0) {
            uptr->DENS &= ~(IO_ST2_556 | IO_ST2_800 | IO_ST2_1600);
            if ((Areg & IO_1732_556) != 0)
              uptr->DENS |= IO_ST2_556;
            if ((Areg & IO_1732_800) != 0)
              uptr->DENS |= IO_ST2_800;
            if (MTdev.iod_type == DEVTYPE_1732_3)
              if ((Areg & IO_1732_1600) != 0)
                uptr->DENS |= IO_ST2_1600;
          }
        }
        /*
         * Make sure STATUS2 values are consistent with actual drive status.
         */
        MTdev.STATUS2 = uptr->DENS & (IO_ST2_556 | IO_ST2_800);
        if ((uptr->flags & UNIT_WPRT) != 0)
          MTdev.STATUS2 &= ~IO_ST2_WENABLE;
        else MTdev.STATUS2 |= IO_ST2_WENABLE;
        if ((uptr->flags & UNIT_7TRACK) != 0)
          MTdev.STATUS2 |= IO_ST2_7TRACK;
        else MTdev.STATUS2 &= ~IO_ST2_7TRACK;
      }
      return IO_REPLY;

    case 0x03:
      if ((uptr == NULL) || (MTdev.iod_type == DEVTYPE_1732_A))
        return IO_REJECT;
      MTdev.iod_LWA = LoadFromMem(IOAreg);
      MTdev.iod_CWA = MTdev.iod_FWA = ++IOAreg;
      MTdev.iod_DSApending = TRUE;
      if ((mt_dev.dctrl & DBG_OPS) != 0)
        mt_DSAtrace(uptr, "setup");

      return IO_REPLY;
  }
  return IO_REJECT;
}

/* Perform I/O initiated through a 1706 buffered data channel */

enum IOstatus MTBDCin(IO_DEVICE *iod, uint16 *data, uint8 reg)
{
  UNIT *uptr = MTdev.iod_unit;

  if ((mt_dev.dctrl & DBG_DTRACE) != 0) {
    int32 u = uptr - mt_dev.units;

    fprintf(DBGOUT,
            "%sMT%d: BDC input to register %d\r\n",
            INTprefix, u, reg);
  }

  /*
   * The framework only passes INP operations for the data register (0x380).
   */
  if (uptr != NULL) {
    if ((MTdev.STATUS & IO_ST_DATA) != 0)
      if (((MTdev.STATUS & IO_ST_READY) != 0) && (MTremain != 0))
        return doMTIn(uptr, data, TRUE);
  }
  return IO_REJECT;
}

enum IOstatus MTBDCout(IO_DEVICE *iod, uint16 *data, uint8 reg)
{
  UNIT *uptr = MTdev.iod_unit;

  if ((mt_dev.dctrl & DBG_DTRACE) != 0) {
    int32 u = uptr - mt_dev.units;

    fprintf(DBGOUT,
            "%sMT%d: BDC output, %04X from register %d\r\n",
            INTprefix, u, IOAreg, reg);
  }

  switch (reg) {
    case 0x00:
      if (uptr != NULL) {
        if ((MTdev.STATUS & IO_ST_READY) != 0)
          return doMTOut(uptr, data, TRUE);
      }
      return IO_REJECT;

    case 0x01:
      return doMTFunction(MTdev.iod_outdev);

    case 0x02:
      break;
  }
  return IO_REJECT;
}

t_stat mt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
  const char helpString[] =
    /****************************************************************************/
    " The %D device is either a 1732-A or 1732-3 magtape controller.\n"
    "1 Hardware Description\n"
    " The %D device consists of either a 1732-A or 1732-3 controller along\n"
    " with 4 tape transports. The type con controller present may be changed\n"
    " by:\n\n"
    "+sim> SET %D 1732-A\n"
    "+sim> SET %D 1732-3\n\n"
    " The first 3 transports (MT0, MT1, MT2) are 9-track drives and MT3 is a\n"
    " 7-track drive. The type of a transport may be changed with:\n\n"
    "+sim> SET %U 9TRACK\n"
    "+sim> SET %U 7TRACK\n\n"
    " Each drive may be individually write-locked or write-enabled with:\n\n"
    "+sim> SET %U LOCKED\n"
    "+sim> SET %U WRITEENABLED\n\n"
    " The 1732-A controller can only perform I/O 1 or 2 bytes at a time. In\n"
    " order to use DMA it must be coupled with a 1706-A. Due to the lack of\n"
    " DMA it can only support 200, 556 and 800 BPI on 9-track transports.\n\n"
    " The 1732-3 is a newer controller which has DMA capability built in. It\n"
    " loses the ability to handle 200 BPI tape but adds the ability to access\n"
    " 1600 BPI phase encoded tapes.\n"
    "2 Equipment Address\n"
    " Magtape controllers are typically set to equipment address 7. This\n"
    " address 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 and\n"
    " STATUS2 always contains the current status of the device as it would be\n"
    " read by an 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);
}
