/* i7090_mt.c: IBM 7090 Magnetic tape controller

   Copyright (c) 2005-2016, Richard Cornwell

   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
   RICHARD CORNWELL 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.

   Magnetic tapes are represented as a series of variable records
   of the form:

        32b byte count
        byte 0
        byte 1
        :
        byte n-2
        byte n-1
        32b byte count

   If the byte count is odd, the record is padded with an extra byte
   of junk.  File marks are represented by a byte count of 0.
*/

#include "i7000_defs.h"
#include "sim_tape.h"

#ifndef NUM_DEVS_MT
#define NUM_DEVS_MT 0
#endif

#if (NUM_DEVS_MT > 0) || defined(MT_CHANNEL_ZERO)

#define BUFFSIZE        (MAXMEMSIZE * CHARSPERWORD)
#define UNIT_MT(x)      UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | \
                        UNIT_S_CHAN(x)
#define MTUF_LDN        (1 << MTUF_V_UF)
#define MTUF_ONLINE     (1 << UNIT_V_UF_31)


/* in u3 is current frame of tape */
/* in u5 holds the commands */
#define MT_RDS          1
#define MT_RDSB         2
#define MT_WRS          3
#define MT_WRSB         4
#define MT_WEF          5
#define MT_BSR          6
#define MT_BSF          7
#define MT_REW          8
#define MT_SDN          9
#define MT_RUN          10
#define MT_SKIP         11      /* Do skip to end of record */
#define MT_WRITE        12      /* Actual transfer operation */
#define MT_SKR          13
#define MT_ERG          14
#define MT_RDB          15
#define MT_LREW         16      /* Low speed rewind */
#define MT_HREW         17      /* High speed rewind */
#define MT_CMDMSK   000037      /* Command being run */
#define MT_RDY      000040      /* Device is ready for command */
#define MT_IDLE     000100      /* Tape still in motion */
#define MT_MARK     000200      /* Hit tape mark */
#define MT_EOT      000400      /* At End Of Tape */
#define MT_RM       001000      /* Hit a record mark character */
#define MT_EOR      002000      /* Set EOR on next record */
#define MT_UNLOAD   004000      /* Unload when rewind done */
#define MT_EGAP     010000      /* Write extended gap on next write */
#define MT_LWR      020000      /* Last command was a write */
#define MT_CLRIND   040000      /* On I7010 flag for SKR to clear indicator */

/* u6 holds the current buffer position */

/* Flags for mt_chan */
#define MTC_SEL     0020        /* Controller executing read/write */
#define MTC_BSY     0040        /* Controller is busy - executing cmd */
#define MTC_UNIT    0017        /* device Channel is on */

/* Timing for tape */
#define IPS             75      /* Inches per second 75 or 112 */
#define HS_IPS         500      /* High speed rewind Inches per second */

#define LD             300
#define HD             150

#define LT_GAP_LEN     ((3 * LD)/ 4)   /* Gap length for low density */
#define HT_GAP_LEN     ((3 * HD)/ 4)   /* Gap length for high density */
#define LT             (1000000/(LD * IPS))   /* Time per char low density */
#define HT             (1000000/(HD * IPS))   /* Time per char high density */
#define LT_GAP_TIM     (LT_GAP_LEN * LT)     /* Time per char low density */
#define HT_GAP_TIM     (HT_GAP_LEN * HT)     /* Time per char high density */

/* Normal frame time */
#define T1             ((uptr->flags & MTUF_LDN) ?LT:HT)
#define T1_us          us_to_ticks(T1)
/* Gap time */
#define T2             ((uptr->flags & MTUF_LDN) ?LT_GAP_TIM:HT_GAP_TIM)
#define T2_us          us_to_ticks(T2)
/* Start time */
#define T3             (((uptr->flags & MTUF_LDN) ?LT_GAP_TIM:HT_GAP_TIM) + 500)
#define T3_us          us_to_ticks(T3)
#define GAP_LEN        ((uptr->flags & MTUF_LDN) ?LT_GAP_LEN:HT_GAP_LEN)

/* Definitions */
uint32              mt_cmd(UNIT *, uint16, uint16);
t_stat              mt_srv(UNIT *);
t_stat              mt_boot(int32, DEVICE *);
void                mt_ini(UNIT *, t_bool);
t_stat              mt_reset(DEVICE *);
t_stat              mt_attach(UNIT *, CONST char *);
t_stat              mt_detach(UNIT *);
t_stat              mt_rew(UNIT * uptr, int32 val, CONST char *cptr,
                        void *desc);
t_stat              mt_tape_density(UNIT * uptr, int32 val, CONST char *cptr,
                        void *desc);
t_stat              mt_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag,
                        const char *cptr);
const char          *mt_description (DEVICE *dptr);
extern t_stat       chan_boot(int32, DEVICE *);
#ifdef I7010
extern uint8        chan_io_status[NUM_CHAN];   /* Channel status */
#endif

#ifdef MT_CHANNEL_ZERO
#define NUM_DEVS (NUM_DEVS_MT + 1)
#else
#define NUM_DEVS (NUM_DEVS_MT)
#endif

/* Channel level activity */
uint8               mt_chan[NUM_CHAN];

/* One buffer per channel */
uint8               mt_buffer[NUM_DEVS][BUFFSIZE];

UNIT                mta_unit[] = {
/* Controller 1 */
#if (NUM_DEVS_MT > 0)
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(1), 0), 0}, /* 9 */
#if (NUM_DEVS_MT > 1)
/* Controller 2 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(2), 0), 0}, /* 9 */
#if (NUM_DEVS_MT > 2)
/* Controller 3 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(3), 0), 0}, /* 9 */
#if (NUM_DEVS_MT > 3)
/* Controller 4 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(4), 0), 0}, /* 9 */
#if (NUM_DEVS_MT > 4)
/* Controller 5 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(5), 0), 0}, /* 9 */
#if (NUM_DEVS_MT > 5)
/* Controller 6 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(6), 0), 0}, /* 9 */
#endif
#endif
#endif
#endif
#endif
#endif
#ifdef MT_CHANNEL_ZERO
/* Controller 7 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 0 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 1 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 2 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 3 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 4 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 5 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 6 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 7 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 8 */
    {UDATA(&mt_srv, UNIT_MT(0), 0), 0}, /* 9 */
#endif
};

MTAB                mt_mod[] = {
    { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED",
        &set_writelock, &show_writelock,   NULL, "Write ring in place" },
    { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED",
        &set_writelock, NULL,   NULL, "no Write ring in place" },
    {MTUF_LDN, 0, "high density", "HIGH", &mt_tape_density, NULL, NULL,
        "556 BPI"},
    {MTUF_LDN, MTUF_LDN, "low density", "LOW", &mt_tape_density, NULL, NULL,
        "200 BPI"},
#ifdef I7090
    {MTUF_ONLINE, 0, "offline", "OFFLINE", NULL, NULL, NULL,
        "Tape offline"},
    {MTUF_ONLINE, MTUF_ONLINE, "online", "ONLINE", NULL, NULL, NULL,
        "Tape Online"},
#endif
    {MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT",
     &sim_tape_set_fmt, &sim_tape_show_fmt, NULL,
       "Set/Display tape format (SIMH, E11, TPC, P7B)"},
   {MTAB_XTD | MTAB_VUN, 0, "LENGTH", "LENGTH",
     &sim_tape_set_capac, &sim_tape_show_capac, NULL,
      "Set unit n capacity to arg MB (0 = unlimited)" },
    {MTAB_XTD | MTAB_VUN, 0, NULL, "REWIND",
     &mt_rew, NULL, NULL, "Rewind tape"
    },
#ifdef I7090
    {MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, "CHAN", "CHAN", &set_chan, &get_chan,
     NULL, "Device Channel"},
#endif
    {0}
};

#ifdef MT_CHANNEL_ZERO
DEVICE              mtz_dev = {
    "MT", &mta_unit[NUM_DEVS_MT * 10], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(NUM_DEVS_MT) | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};
#endif

#if (NUM_DEVS_MT > 0)
DEVICE              mta_dev = {
    "MTA", mta_unit, NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(0) | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

#if (NUM_DEVS_MT > 1)
DEVICE              mtb_dev = {
    "MTB", &mta_unit[10], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(1) | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

#if (NUM_DEVS_MT > 2)
DEVICE              mtc_dev = {
    "MTC", &mta_unit[20], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(2) | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

#if (NUM_DEVS_MT > 3)
DEVICE              mtd_dev = {
    "MTD", &mta_unit[30], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 36,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(3) | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

#if (NUM_DEVS_MT > 4)
DEVICE              mte_dev = {
    "MTE", &mta_unit[40], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(4) | DEV_DIS | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};

#if (NUM_DEVS_MT > 5)
DEVICE              mtf_dev = {
    "MTF", &mta_unit[50], NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mt_dib, DEV_BUF_NUM(5) | DEV_DIS | DEV_DISABLE | DEV_DEBUG | DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};
#endif
#endif
#endif
#endif
#endif
#endif


uint8               parity_table[64] = {
    /* 0    1    2    3    4    5    6    7 */
    0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100,
    0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000,
    0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000,
    0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100,
    0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000,
    0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100,
    0000, 0100, 0100, 0000, 0100, 0000, 0000, 0100,
    0100, 0000, 0000, 0100, 0000, 0100, 0100, 0000
};

/* Rewind tape drive */
t_stat
mt_rew(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    /* If drive is offline or not attached return not ready */
    if ((uptr->flags & (UNIT_ATT | MTUF_ONLINE)) == 0)
        return SCPE_NOATT;
    /* Check if drive is ready to recieve a command */
    if ((uptr->u5 & MT_RDY) == 0)
        return STOP_IOCHECK;
    return sim_tape_rewind(uptr);
}

/* Start off a mag tape command */
uint32 mt_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    DEVICE             *dptr = find_dev_from_unit(uptr);
    int                 time = us_to_ticks(100);
    int                 unit = dev & 017;

    unit -= mt_dib.addr & 017;  /* Adjust to origin zero */
    if (unit == 10)
        unit = 0;
    /* Make sure valid drive number */
    if (unit > NUM_UNITS_MT || unit < 0)
        return SCPE_NODEV;
    uptr += unit;
    /* If unit disabled return error */
    if (uptr->flags & UNIT_DIS) {
        return SCPE_NODEV;
    }

    /* Check status of the drive */

    /* Can't do nothing if controller is busy */
    if (mt_chan[chan] & MTC_BSY) {
        return SCPE_BUSY;
    }
    /* If drive is offline or not attached return not ready */
    if ((uptr->flags & (UNIT_ATT | MTUF_ONLINE)) !=
        (UNIT_ATT | MTUF_ONLINE)) {
        sim_messagef(SCPE_OK, "Attempt to access offline unit %s%d\n\r", dptr->name, unit);
        return SCPE_IOERR;
    }
    /* Check if drive is ready to recieve a command */
    if ((uptr->u5 & MT_RDY) == 0) {
        /* Return indication if not ready and doing TRS */
        if (cmd == IO_TRS) {
            return SCPE_IOERR;
        } else {
            return SCPE_BUSY;
        }
    }
    uptr->u5 &= ~(MT_CMDMSK | MT_RDY);
    time = us_to_ticks(12000);
    if ((uptr->u5 & MT_IDLE) == 0)
        time = us_to_ticks(15000);
    switch (cmd) {
    case IO_RDS:
        if (sim_tape_bot(uptr))
            time = us_to_ticks(21000);
        if (mt_chan[chan] & MTC_SEL) {
            uptr->u5 |= MT_RDY;
            return SCPE_BUSY;
        }

#ifdef I701
        uptr->u5 |= MT_RDSB;
#else
        if (dev & 020)
            uptr->u5 |= MT_RDSB;
        else
            uptr->u5 |= MT_RDS;
#endif
        chan_set_sel(chan, 0);
        chan_clear_status(chan);
        mt_chan[chan] = MTC_BSY | MTC_SEL | unit;
        uptr->u5 &= ~(MT_RM|MT_EOR|MT_EGAP);
        uptr->u6 = -1;
        uptr->hwmark = -1;
#if I7010 | I7080
        chan_set(chan, STA_TWAIT);
#endif
        sim_debug(DEBUG_CMD, dptr, "RDS %s unit=%d %d\n",
                  ((uptr->u5 & MT_CMDMSK) == MT_RDS) ? "BCD" : "Binary",
                         unit, dev);
        break;

    case IO_WRS:
        if (sim_tape_bot(uptr))
            time = us_to_ticks(40000);
        if (mt_chan[chan] & MTC_SEL) {
            uptr->u5 |= MT_RDY;
            return SCPE_BUSY;
        }
        if (sim_tape_wrp(uptr)) {
            sim_debug(DEBUG_EXP, dptr,
                      "WRS %d attempted on locked tape\n", unit);
            uptr->u5 |= MT_RDY;
            return SCPE_IOERR;
        }
#ifdef I701
        uptr->u5 |= MT_WRSB;
#else
        if (dev & 020)
            uptr->u5 |= MT_WRSB;
        else
            uptr->u5 |= MT_WRS;
#endif
        time += T2_us;
        uptr->u6 = 0;
        uptr->hwmark = 0;
        chan_set_sel(chan, 1);
        chan_clear_status(chan);
        mt_chan[chan] = MTC_BSY | MTC_SEL | unit;
        uptr->u5 &= ~(MT_MARK | MT_EOT);
#if I7010 | I7080
        chan_set(chan, STA_TWAIT);
#endif
        sim_debug(DEBUG_CMD, dptr, "WRS %s unit=%d %d\n",
                  ((uptr->u5 & MT_CMDMSK) == MT_WRS) ? "BCD" : "Binary",
                   unit, dev);
        break;

    case IO_RDB:
        if (mt_chan[chan] & MTC_SEL) {
            uptr->u5 |= MT_RDY;
            return SCPE_BUSY;
        }

        uptr->u5 |= MT_RDB;
        chan_set_sel(chan, 0);
        chan_clear_status(chan);
        mt_chan[chan] = MTC_BSY | MTC_SEL | unit;
        uptr->u5 &= ~(MT_RM|MT_EOR|MT_EGAP);
        uptr->u6 = -1;
        uptr->hwmark = -1;
#if I7010 | I7080
        chan_set(chan, STA_TWAIT);
#endif
        sim_debug(DEBUG_CMD, dptr, "RDB unit=%d %d\n", unit, dev);
        break;

    case IO_WEF:
        if (sim_tape_bot(uptr))
            time = us_to_ticks(40000);
        uptr->u5 &= ~(MT_EOT|MT_MARK);
        if (sim_tape_wrp(uptr)) {
            sim_debug(DEBUG_EXP, dptr,
                      "WRS %d attempted on locked tape\n", unit);
            uptr->u5 |= MT_RDY;
            return SCPE_IOERR;
        }
        uptr->u5 |= MT_WEF;
#if I7010
        chan_set_sel(chan, 1);
        chan_clear_status(chan);
        mt_chan[chan] = MTC_BSY | MTC_SEL | unit;
        chan_set(chan, STA_TWAIT);
#else
        mt_chan[chan] = MTC_BSY;
#endif
        sim_debug(DEBUG_CMD, dptr, "WEF unit=%d\n", unit);
        break;

    case IO_BSR:
        uptr->u5 &= ~(MT_MARK);
        /* Check if at load point, quick return if so */
        if (sim_tape_bot(uptr)) {
            sim_debug(DEBUG_CMD, dptr, "BSR unit=%d at BOT\n", unit);
            uptr->u5 |= MT_RDY;
            uptr->u3 = 0;
            chan_set(chan, CHS_BOT);
            return SCPE_OK;
        }
        uptr->u5 |= MT_BSR;
        mt_chan[chan] = MTC_BSY;
        sim_debug(DEBUG_CMD, dptr, "BSR unit=%d\n", unit);
        break;

    case IO_BSF:
        uptr->u5 &= ~(MT_MARK);
        /* Check if at load point, quick return if so */
        if (sim_tape_bot(uptr)) {
            sim_debug(DEBUG_CMD, dptr, "BSF unit=%d at BOT\n", unit);
            uptr->u5 |= MT_RDY;
            uptr->u3 = 0;
            chan_set(chan, CHS_BOT);
            return SCPE_OK;
        }
        uptr->u5 |= MT_BSF;
        mt_chan[chan] = MTC_BSY;
        sim_debug(DEBUG_CMD, dptr, "BSF unit=%d\n", unit);
        break;

    case IO_SKR:
        if (sim_tape_bot(uptr))
            time = us_to_ticks(21000);
        uptr->u5 &= ~(MT_MARK|MT_EGAP);
        uptr->u5 |= MT_SKR;
        mt_chan[chan] = MTC_BSY;
        sim_debug(DEBUG_CMD, dptr, "SKR unit=%d\n", unit);
        break;

    case IO_ERG:
        sim_debug(DEBUG_CMD, dptr, "ERG unit=%d\n", unit);
#ifdef I7080
        uptr->u5 &= ~(MT_MARK);
        uptr->u5 |= MT_ERG;
        mt_chan[chan] = MTC_BSY;
        chan_set(chan, STA_TWAIT);
        break;
#else
        uptr->u5 |= MT_EGAP|MT_RDY;     /* Command is quick */
        return SCPE_OK;
#endif

    case IO_REW:
        uptr->u5 &= ~(MT_EOT|MT_MARK|MT_EGAP);
        /* Check if at load point, quick return if so */
        if (sim_tape_bot(uptr)) {
            sim_debug(DEBUG_CMD, dptr, "REW unit=%d at BOT\n", unit);
            uptr->u5 |= MT_RDY;
            uptr->u3 = 0;
            return SCPE_OK;
        }
        time = 1000;
        uptr->u5 |= MT_REW;
        mt_chan[chan] = MTC_BSY;
        sim_debug(DEBUG_CMD, dptr, "REW unit=%d\n", unit);
        sim_cancel(uptr);
        sim_activate(uptr, time);
        return SCPE_OK;

    case IO_RUN:
        uptr->u5 &= ~(MT_EOT|MT_MARK|MT_EGAP);
        chan_clear_status(chan);
        uptr->u5 |= MT_RUN;
        mt_chan[chan] = MTC_BSY;
        time = 1000;
        sim_debug(DEBUG_CMD, dptr, "RUN unit=%d\n", unit);
        sim_cancel(uptr);
        sim_activate(uptr, time);
        return SCPE_OK;

    case IO_SDL:
        uptr->u5 |= MT_RDY;     /* Command is quick */
        uptr->flags |= MTUF_LDN;
        sim_tape_set_dens (uptr, MT_DENS_200, NULL, NULL);
        sim_debug(DEBUG_CMD, dptr, "SDN unit=%d low\n", unit);
        return SCPE_OK;

    case IO_SDH:
        uptr->u5 |= MT_RDY;     /* Command is quick */
        uptr->flags &= ~MTUF_LDN;
        sim_tape_set_dens (uptr, MT_DENS_556, NULL, NULL);
        sim_debug(DEBUG_CMD, dptr, "SDN unit=%d high\n", unit);
        return SCPE_OK;

    case IO_DRS:
        uptr->flags &= ~MTUF_ONLINE;
        uptr->u5 |= MT_RDY;     /* Command is quick */
        sim_debug(DEBUG_CMD, dptr, "DRS unit=%d\n", unit);
        return SCPE_OK;

    case IO_TRS:
        uptr->u5 |= MT_RDY;     /* Get here we are ready */
        sim_debug(DEBUG_CMD, dptr, "TRS unit=%d\n", unit);
        return SCPE_OK;
    }
    sim_cancel(uptr);
    sim_activate(uptr, time);
    return SCPE_OK;
}


#if I7090 | I704 | I701
/* Read a word from tape, used during boot read */
int
mt_read_buff(UNIT * uptr, int cmd, DEVICE * dptr, t_uint64 *word)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 bufnum = GET_DEV_BUF(dptr->flags);
    int                 i;
    uint8               ch;
    int                 mode = 0;
    int                 mark = 1;
    int                 parity = 0;

    uptr->u5 &= ~MT_MARK;
    if (cmd == MT_RDS)
        mode = 0100;

    *word = 0;
    for(i = CHARSPERWORD-1; i >= 0 && uptr->u6 < (int32)uptr->hwmark; i--) {
        ch = mt_buffer[bufnum][uptr->u6++];
        /* Do BCD translation */
        if ((parity_table[ch & 077] ^ (ch & 0100) ^ mode) == 0) {
                parity = 1;
        }
        ch &= 077;
        /* Not needed on decimal machines */
        if (mode) {
            /* Map BCD to internal format */
            ch ^= (ch & 020) << 1;
            if (ch == 012)
                ch = 0;
            if (ch == 017 && mark) {
                chan_set_error(chan);   /* Force CRC error. */
                ch = 0;
                mark = 0;
                uptr->u6++;     /* Skip next character */
                i--;
            }
        }
        if (i >= 0)
            *word |= ((t_uint64) ch) << (6 * i);
    }

    if (parity) {
        chan_set_error(chan);   /* Force redundency error */
        return 0;
    }
    return 1;
}
#endif

/* Map simH errors into machine errors */
t_stat mt_error(UNIT * uptr, int chan, t_stat r, DEVICE * dptr)
{
    switch (r) {
    case MTSE_OK:               /* no error */
        break;

    case MTSE_TMK:              /* tape mark */
        sim_debug(DEBUG_EXP, dptr, "MARK ");
        chan_set_eof(chan);
        break;

    case MTSE_WRP:              /* write protected */
    case MTSE_UNATT:            /* unattached */
        sim_debug(DEBUG_EXP, dptr, "ATTENTION %d ", r);
        chan_set_attn(chan);
        break;

    case MTSE_IOERR:            /* IO error */
    case MTSE_FMT:              /* invalid format */
    case MTSE_RECE:             /* error in record */
        chan_set_error(chan);   /* Force redundency error */
        chan_set_attn(chan);    /* Set error */
        sim_debug(DEBUG_EXP, dptr, "ERROR %d ", r);
        break;
    case MTSE_BOT:              /* beginning of tape */
        chan_set(chan, CHS_BOT);        /* Set flag */
        sim_debug(DEBUG_EXP, dptr, "BOT ");
        break;
    case MTSE_INVRL:            /* invalid rec lnt */
    case MTSE_EOM:              /* end of medium */
        uptr->u5 |= MT_EOT;
        sim_debug(DEBUG_EXP, dptr, "EOT ");
#ifdef I7010
        chan_set_attn(chan);    /* Set error */
#endif
        break;
    }
    return SCPE_OK;
}

/* Handle processing of tape requests. */
t_stat mt_srv(UNIT * uptr)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    DEVICE             *dptr = find_dev_from_unit(uptr);
    int                 unit = (uptr - dptr->units) & MTC_UNIT;
    int                 cmd = uptr->u5 & MT_CMDMSK;
    int                 bufnum = GET_DEV_BUF(dptr->flags);
    t_mtrlnt            reclen;
    t_stat              r = SCPE_ARG;   /* Force error if not set */
    uint8               ch;
    int                 mode = 0;
#ifdef I7010
    extern uint8        astmode;
#endif

    /* Call channel proccess to make sure data is ready */
    chan_proc();

    /* Channel has disconnected, abort current read. */
    if ((mt_chan[chan] & 037) == (MTC_SEL | unit) &&
            chan_test(chan, DEV_DISCO)) {
        uptr->u5 &= ~MT_CMDMSK;
        reclen = uptr->hwmark;
        if (cmd == MT_WRS || cmd == MT_WRSB) {
            if (uptr->u6 > 0) {
                 uptr->u3 += GAP_LEN;
                 sim_debug(DEBUG_DETAIL, dptr,
                        "Write flush unit=%d %s Block %d chars\n",
                         unit, (cmd == MT_WRS) ? "BCD" : "Binary", reclen);
                 r = sim_tape_wrrecf(uptr, &mt_buffer[bufnum][0], reclen);
                 mt_error(uptr, chan, r, dptr);      /* Record errors */
            }
        } else if (cmd == MT_RDS || cmd == MT_RDSB) {
            sim_debug(DEBUG_DETAIL, dptr,
                        "Read flush unit=%d %s at %d Block %d chars\n",
                         unit, (cmd == MT_RDS) ? "BCD" : "Binary", uptr->u6, reclen);
            /* Keep moving until end of block */
            if (uptr->u6 < (int32)reclen ) {
                reclen -= uptr->u6;
                uptr->u3 += reclen;
                uptr->u5 |= MT_SKIP|MT_IDLE;
                uptr->u6 = 0;
                uptr->hwmark = 0;
                chan_clear(chan, DEV_WEOR );
                sim_activate(uptr, reclen * T1_us);
                return SCPE_OK;
            } else {
#ifndef I7010
                if (uptr->u5 & MT_MARK) {
                /* We hit tapemark, Back up so next read hits it */
                /* Or write starts just before it */
                /* This is due to SIMH returning mark after read */
                    (void) sim_tape_sprecr(uptr, &reclen);
                    uptr->u5 &= ~MT_MARK;
                    uptr->u3 -= GAP_LEN + reclen;
                }
#endif
            }
        }
        /* Allow time for tape to be restarted, before stop */
        sim_activate(uptr,  us_to_ticks(500));
        uptr->u6 = 0;
        uptr->hwmark = 0;
        sim_debug(DEBUG_CHAN, dptr, "Disconnect unit=%d\n", unit);
        uptr->u5 |= MT_IDLE|MT_RDY;
        mt_chan[chan] = 0;
        chan_clear(chan, DEV_DISCO | DEV_WEOR | DEV_SEL);
#if I7010 | I7080
        chan_clear(chan, STA_TWAIT);
#endif
        return SCPE_OK;
    }

    uptr->u5 &= ~MT_IDLE;
    switch (cmd) {
    case 0:                     /* No command, stop tape */
        uptr->u5 |= MT_RDY;     /* Ready since command is done */
        mt_chan[chan] &= ~MTC_BSY;
        sim_debug(DEBUG_DETAIL, dptr, "Idle unit=%d\n", unit);
        return SCPE_OK;

    case MT_SKIP:               /* Record skip done, enable tape drive */
        uptr->u5 &= ~MT_CMDMSK;
        uptr->u5 |= MT_RDY | MT_IDLE;
#if I7090 | I704 | I701
        chan_clear(chan, DEV_SEL);
#else
        chan_clear(chan, DEV_SEL|STA_TWAIT);
#endif
        mt_chan[chan] = 0;
        sim_debug(DEBUG_DETAIL, dptr, "Skip unit=%d\n", unit);
        /* Allow time for tape to be restarted, before stop */
        sim_activate(uptr,  us_to_ticks(500));
        return SCPE_OK;

    case MT_RDS:
         mode = 0100;
         /* Fall through */

    case MT_RDSB:
        uptr->u5 &= ~MT_LWR;     /* Not write command */
        /* Post EOR */
        if (uptr->u5 & MT_EOR) {
            sim_debug(DEBUG_DETAIL, dptr, "Read unit=%d post EOR\n", unit);
            chan_set(chan, DEV_REOR);
            uptr->u5 &= ~ MT_EOR;
            sim_activate(uptr, T1_us);
            return SCPE_OK;
        }

        /* If tape mark pending, return it */
        if (chan_test(chan, DEV_FULL) == 0 && uptr->u5 & MT_MARK) {
            sim_debug(DEBUG_DETAIL, dptr, "Read unit=%d post ", unit);
            uptr->u5 &= ~(MT_CMDMSK|MT_MARK);
#ifdef I7010
            if (astmode) {
                ch = mode?017:054;
                chan_write_char(chan, &ch, DEV_REOR);
                if (mode) {
                   chan_clear(chan, STA_TWAIT);
                   sim_activate(uptr, us_to_ticks(100));
                   return SCPE_OK;
                }
            }
#endif
            chan_set_attn(chan);
            sim_activate(uptr, us_to_ticks(100));
            return mt_error(uptr, chan, MTSE_TMK, dptr);
        }
        /* If at end of record, fill buffer */
        if (uptr->u6 == (int32)uptr->hwmark) {
            sim_debug(DEBUG_DETAIL, dptr, "Read unit=%d ", unit);
            uptr->u3 += GAP_LEN;
            if ((r = sim_tape_rdrecf(uptr, &mt_buffer[bufnum][0], &reclen,
                                BUFFSIZE)) != MTSE_OK) {
                if (r == MTSE_TMK && uptr->u6 != -1) {
                    sim_debug(DEBUG_DETAIL, dptr, "pend TM\n");
                    uptr->u5 |= MT_MARK;
                    r = MTSE_OK;
                } else {
                    sim_debug(DEBUG_DETAIL, dptr, "error=%d\n", r);
                    uptr->u5 &= ~MT_CMDMSK;
#ifdef I7010
                    /* Translate TM characters for 7010 */
                    if (r == MTSE_TMK && astmode) {
                        sim_debug(DEBUG_DETAIL, dptr, "Read TM ");
                        ch = mode?017:054;
                        chan_write_char(chan, &ch, 0);
                        chan_set_attn(chan);
                        chan_set(chan, DEV_REOR);
                        chan_clear(chan, STA_TWAIT);
                        if (mode) {
                           sim_activate(uptr, T1_us);
                           return SCPE_OK;
                        }
                        chan_set_error(chan);
                    }
#else
                    chan_set(chan, DEV_REOR);
                    chan_set_attn(chan);
#endif
                }
                sim_activate(uptr, T1_us);
                return mt_error(uptr, chan, r, dptr);
            }
            uptr->u6 = 0;
            uptr->hwmark = reclen;
            chan_clear(chan, CHS_EOF|CHS_ERR);
            sim_debug(DEBUG_DETAIL, dptr, "%s Block %d chars\n",
                      (cmd == MT_RDS) ? "BCD" : "Binary", reclen);
#ifdef I7010
            if (mode && mt_buffer[bufnum][0] == 017)
                chan_set_eof(chan);
#endif

        }

        ch = mt_buffer[bufnum][uptr->u6++];
        uptr->u3++;
        /* Do BCD translation */
        if ((parity_table[ch & 077] ^ (ch & 0100) ^ mode) == 0) {
            sim_debug(DEBUG_DETAIL, dptr, "%s parity error %d %03o\n",
                      (cmd == MT_RDS) ? "BCD" : "Binary", uptr->u6-1, ch);
#ifdef I7010
            if (astmode)
                ch = 054;
#endif
            chan_set_error(chan);

        }
#if I7090 | I704 | I701
        /* Not needed on decimal machines */
        if (mode) {
            /* Map BCD to internal format */
            ch ^= (ch & 020) << 1;
            if (ch == 012)
                ch = 0;
            if (ch == 017) {
                chan_set_error(chan);   /* Force CRC error. */
                if ((uptr->u5 & MT_RM) == 0) {
                     ch = 0;
                     uptr->u5 |= MT_RM;
                     mt_buffer[bufnum][uptr->u6] = 0;
                }
            }
        }
#endif
#ifdef I7010
        if (mode) {
            if (ch == 0120)
                ch = 0;
        }
#endif
        ch &= 077;

        /* Convert one word. */
        switch (chan_write_char(chan, &ch, 0)) {
        case END_RECORD:
            sim_debug(DEBUG_DATA, dptr, "Read unit=%d EOR\n", unit);
            /* If not read whole record, skip till end */
            uptr->u5 |= MT_EOR;
            if (uptr->u6 < (int32)uptr->hwmark) {
                sim_activate(uptr, (uptr->hwmark-uptr->u6) * T1_us);
                uptr->u3 += (uptr->hwmark - uptr->u6);
                uptr->u6 = uptr->hwmark;    /* Force read next record */
            }
            sim_activate(uptr, T1_us);
            break;

        case DATA_OK:
            sim_debug(DEBUG_DATA, dptr, "Read data unit=%d %d %02o\n",
                      unit, uptr->u6, ch);
            if (uptr->u6 >= (int32)uptr->hwmark)  /* In IRG */
                uptr->u5 |= MT_EOR;
            sim_activate(uptr, T1_us);
            break;

        case TIME_ERROR:
            sim_debug(DEBUG_DATA, dptr, "Read unit=%d timeout\n", unit);
            uptr->u3 += (uptr->hwmark - uptr->u6);
            uptr->u5 &= ~MT_CMDMSK;
            uptr->u5 |= MT_SKIP;
            sim_activate(uptr, ((uptr->hwmark - uptr->u6) * T1_us) + T2_us);
            uptr->u6 = uptr->hwmark;        /* Force read next record */
            break;
        }
        return SCPE_OK;


    /* Check mode */
    case MT_WRS:
        mode = 0100;
        /* fall through */
    case MT_WRSB:
        uptr->u5 |= MT_LWR;     /* write command */
        if (uptr->u5 & MT_EGAP) {
            sim_debug(DEBUG_DETAIL, dptr, "Write extended Gap unit=%d\n", unit);
            uptr->u5 &= ~MT_EGAP;
            r = sim_tape_wrgap(uptr, 35);
            sim_activate(uptr, 10*T3_us);
            return SCPE_OK;
        }

        switch (chan_read_char(chan, &ch,
                          (uptr->u6 > BUFFSIZE) ? DEV_WEOR : 0)) {
        case TIME_ERROR:
#if I7090 | I701 | I704
            uptr->u5 &= ~MT_CMDMSK;
            uptr->u5 |= MT_SKIP;
            /* If no data was written, simulate a write gap */
            if (uptr->u6 == 0) {
                r = sim_tape_wrgap(uptr, 35);
                if (r != MTSE_OK) {
                    mt_error(uptr, chan, r, dptr);  /* Record errors */
                    return SCPE_OK;
                }
            }
#endif
            /* fall through */

        case END_RECORD:
            if (uptr->u6 > 0) { /* Only if data in record */
                reclen = uptr->hwmark;
                sim_debug(DEBUG_DETAIL, dptr,
                        "Write unit=%d %s Block %d chars\n",
                         unit, (cmd == MT_WRS) ? "BCD" : "Binary", reclen);
                r = sim_tape_wrrecf(uptr, &mt_buffer[bufnum][0], reclen);
                uptr->u3 += GAP_LEN;
                uptr->u6 = 0;
                uptr->hwmark = 0;
                mt_error(uptr, chan, r, dptr);  /* Record errors */
            }
            sim_activate(uptr, T2_us);
            return SCPE_OK;
        case DATA_OK:
            /* Copy data to buffer */
            ch &= 077;
#if I7090 | I701 | I704
            /* Not needed on decimal machines */
            if (mode) {
                /* Do BCD translation */
                ch ^= (ch & 020) << 1;
                if (ch == 0)
                    ch = 012;
            }
#endif
            ch |= mode ^ parity_table[ch] ^ 0100;
            mt_buffer[bufnum][uptr->u6++] = ch;
            uptr->u3++;
            sim_debug(DEBUG_DATA, dptr, "Write data unit=%d %d %02o\n",
                      unit, uptr->u6, ch);
            uptr->hwmark = uptr->u6;
            break;
        }
        sim_activate(uptr, T1_us);
        return SCPE_OK;

    case MT_RDB:
        uptr->u5 &= ~MT_LWR;     /* not write command */
        /* If tape mark pending, return it */
        if (chan_test(chan, DEV_FULL) == 0 && uptr->u5 & MT_MARK) {
            sim_debug(DEBUG_DETAIL, dptr, "Read unit=%d post ", unit);
            uptr->u5 &= ~(MT_CMDMSK|MT_MARK);
            mt_chan[chan] &= MTC_BSY;
            chan_clear(chan, DEV_SEL);
            sim_activate(uptr, us_to_ticks(100));
            return mt_error(uptr, chan, MTSE_TMK, dptr);
        }
        /* If at end of record, fill buffer */
        if (uptr->u6 == (int32)uptr->hwmark) {
            sim_debug(DEBUG_DETAIL, dptr, "Read unit=%d ", unit);
            if ((r = sim_tape_rdrecr(uptr, &mt_buffer[bufnum][0], &reclen,
                                BUFFSIZE)) != MTSE_OK) {
                uptr->u3 -= GAP_LEN;
                sim_activate(uptr, T2_us);
                if (r == MTSE_TMK && uptr->u6 != -1) {
                    sim_debug(DEBUG_DETAIL, dptr, "pend TM\n");
                    uptr->u5 |= MT_MARK;
                    r = MTSE_OK;
                } else {
                    sim_debug(DEBUG_DETAIL, dptr, "error=%d\n", r);
                    uptr->u6 = uptr->hwmark;
                    uptr->u5 &= ~MT_CMDMSK;
                    chan_set_attn(chan);
                    chan_clear(chan, DEV_SEL);
                    mt_chan[chan] &= MTC_BSY;
                }
                return mt_error(uptr, chan, r, dptr);
            }
            uptr->u6 = 0;
            uptr->hwmark = reclen;
            chan_clear(chan, CHS_EOF|CHS_ERR);
            sim_debug(DEBUG_DETAIL, dptr, "Binary Block %d chars\n", reclen);
        }

        ch = mt_buffer[bufnum][uptr->u6++];
        uptr->u3--;
        /* Do BCD translation */
        if ((parity_table[ch & 077] ^ (ch & 0100) ^ mode) == 0) {
            chan_set_error(chan);
            chan_set_attn(chan);
        }
        ch &= 077;

        /* Convert one word. */
        switch (chan_write_char(chan, &ch,
                            (uptr->u6 >= (int32)uptr->hwmark) ? DEV_REOR : 0)) {
        case END_RECORD:
            sim_debug(DEBUG_DATA, dptr, "Read unit=%d EOR\n", unit);
            if (uptr->u6 >= (int32)uptr->hwmark) {
                uptr->u5 &= ~MT_CMDMSK;
                uptr->u5 |= MT_SKIP;
                uptr->u3 -= (uptr->hwmark-uptr->u6);
                sim_activate(uptr, (uptr->hwmark-uptr->u6) * T1_us);
                chan_set(chan, DEV_REOR);
                uptr->u6 = uptr->hwmark;    /* Force read next record */
                break;
            }
            /* fall through */

        case DATA_OK:
            sim_debug(DEBUG_DATA, dptr, "Read data unit=%d %d %02o\n",
                      unit, uptr->u6, ch);
            if (uptr->u6 >= (int32)uptr->hwmark) { /* In IRG */
                uptr->u3 -= (uptr->hwmark-uptr->u6);
                sim_activate(uptr, T2_us);
            } else
                sim_activate(uptr, T1_us);
            break;

        case TIME_ERROR:
            uptr->u5 &= ~MT_CMDMSK;
            uptr->u5 |= MT_SKIP;
            uptr->u3 -= (uptr->hwmark-uptr->u6);
            sim_activate(uptr, (uptr->hwmark-uptr->u6) * T1_us);
            uptr->u6 = uptr->hwmark;        /* Force read next record */
            break;
        }
        return SCPE_OK;

    case MT_WEF:
        uptr->u5 &= ~MT_LWR;     /* not write command */
        if (uptr->u5 & MT_EGAP) {
            sim_debug(DEBUG_DETAIL, dptr, "Write extended Gap unit=%d\n", unit);
            uptr->u5 &= ~MT_EGAP;
            r = sim_tape_wrgap(uptr, 35);
            sim_activate(uptr, 10*T3_us);
            return SCPE_OK;
        }
        sim_debug(DEBUG_DETAIL, dptr, "Write Mark unit=%d\n", unit);
        uptr->u5 &= ~(MT_CMDMSK|MT_MARK);
        uptr->u5 |= (MT_RDY);
        r = sim_tape_wrtmk(uptr);
        uptr->u3 += GAP_LEN;
        mt_chan[chan] &= ~MTC_BSY;
        sim_activate(uptr, T2_us);
#if I7010 | I7080
        chan_set(chan, DEV_REOR);
#endif
        break;

    case MT_BSR:
        sim_debug(DEBUG_DETAIL, dptr, "Backspace rec unit=%d ", unit);
        /* If last command was a write, put extended gap on tape */
        if (uptr->u5 & MT_LWR) {
            sim_debug(DEBUG_DETAIL, dptr, "Write extended Gap unit=%d bsr\n", unit);
            r = sim_tape_wrgap(uptr, 35);
            uptr->u5 &= ~MT_LWR;
            sim_activate(uptr, 10*T3_us);
            return SCPE_OK;
        }

        /* Clear tape mark, command, idle since we will need to change dir */
        uptr->u5 &= ~(MT_CMDMSK | MT_EOT | MT_RDY | MT_LWR);
        r = sim_tape_sprecr(uptr, &reclen);
        if (r != MTSE_BOT)
            uptr->u3 -= GAP_LEN;
        mt_chan[chan] &= ~MTC_BSY;
        if (r == MTSE_TMK) {
#ifdef I7080
            chan_set_eof(chan);
#endif
            sim_debug(DEBUG_DETAIL, dptr, "MARK\n");
            sim_activate(uptr, T2_us);
            return SCPE_OK;
        }
        sim_debug(DEBUG_DETAIL, dptr, "%d \n", reclen);
        uptr->u3 -= reclen;
        sim_activate(uptr, T2_us + (reclen * T1_us));
        return SCPE_OK;

    case MT_BSF:
        uptr->u5 &= ~(MT_IDLE | MT_RDY | MT_EOT | MT_LWR);
        r = sim_tape_sprecr(uptr, &reclen);
        if (r != MTSE_BOT)
            uptr->u3 -= GAP_LEN;
        /* If we hit mark or end of tape */
        if (r == MTSE_TMK || r == MTSE_BOT) {
            sim_debug(DEBUG_DETAIL, dptr, "Backspace file unit=%d\n", unit);
            uptr->u5 &= ~MT_CMDMSK;
            mt_chan[chan] &= ~MTC_BSY;
            sim_activate(uptr, T2_us);
        } else {
            uptr->u3 -= reclen;
            sim_debug(DEBUG_DETAIL, dptr, "Backspace file record unit=%d\n", unit);
            sim_activate(uptr, T2_us + (reclen * T1_us));
        }
        return SCPE_OK;

    case MT_SKR:
        sim_debug(DEBUG_DETAIL, dptr, "Skip rec unit=%d ", unit);
        if (uptr->u5 & MT_CLRIND) {
            sim_debug(DEBUG_DETAIL, dptr, "clear ind\n");
#if I7010
            if ((uptr->u5 & MT_MARK) == 0) {
                chan_clear(chan, STA_PEND);
            }
#endif
            chan_clear(chan, STA_TWAIT);
            uptr->u5 &= ~(MT_CMDMSK | MT_EOT | MT_LWR | MT_MARK | MT_CLRIND);
            uptr->u5 |= (MT_RDY | MT_IDLE);
            mt_chan[chan] &= ~MTC_BSY;
            sim_activate(uptr, (uptr->u6 * T1_us) + T2_us);
            return SCPE_OK;
        }
        uptr->u5 |= (MT_CLRIND);
        r = sim_tape_sprecf(uptr, &reclen);
        uptr->u3 += GAP_LEN;
        uptr->u6 = reclen;
#if I7080
        chan_clear(chan, STA_TWAIT);
#endif
#ifdef I7010
        chan_set(chan, STA_PEND);
#endif
        /* We are like read that transfers nothing */
        chan_set(chan, DEV_REOR);
        /* We don't set EOF on SKR */
        if (r == MTSE_TMK) {
            sim_debug(DEBUG_DETAIL, dptr, "MARK\n");
            uptr->u5 |= MT_MARK;
            uptr->u6 = 1;
            sim_activate(uptr, 2*T1_us);
            return SCPE_OK;
        }
        sim_debug(DEBUG_DETAIL, dptr, "%d\n", reclen);
        sim_activate(uptr, 2*T1_us);
        break;

    case MT_ERG:
        sim_debug(DEBUG_DETAIL, dptr, "Erase unit=%d\n", unit);
        uptr->u5 &= ~(MT_CMDMSK|MT_MARK|MT_LWR);
        uptr->u5 |= (MT_RDY | MT_IDLE);
#if I7010 | I7080
        chan_clear(chan, STA_TWAIT);
#endif
        r = sim_tape_wrgap(uptr, 35);
        uptr->u3 += GAP_LEN;
        mt_chan[chan] &= ~MTC_BSY;
        sim_activate(uptr, 10*T3_us);
        break;

    case MT_REW:
        sim_debug(DEBUG_DETAIL, dptr, "Rewind unit=%d %d %d\n", unit, uptr->u3,
                   uptr->u3 / ((uptr->flags & MTUF_LDN) ? 200 : 555) / 1200);
        uptr->u5 &= ~(MT_CMDMSK | MT_IDLE | MT_RDY | MT_LWR);
        if ((uptr->u3 / ((uptr->flags & MTUF_LDN) ? 200 : 555) / 1200) > 2) {
            uptr->u5 |= MT_HREW;
            sim_activate(uptr, us_to_ticks(5000000));
        } else {
            uptr->u5 |= MT_LREW;
            sim_activate(uptr, 300);
        }
        mt_chan[chan] &= ~MTC_BSY;
        break;

    case MT_RUN:
        sim_debug(DEBUG_DETAIL, dptr, "Unload unit=%d\n", unit);
        uptr->u5 &= ~(MT_CMDMSK | MT_IDLE | MT_RDY | MT_LWR);
        uptr->u5 |= MT_UNLOAD;
        if ((uptr->u3 / ((uptr->flags & MTUF_LDN) ? 200 : 555) / 1200) > 2) {
            uptr->u5 |= MT_HREW;
            sim_activate(uptr, us_to_ticks(5000000));
        } else {
            uptr->u5 |= MT_LREW;
            sim_activate(uptr, 300);
        }
        mt_chan[chan] &= ~MTC_BSY;
        return SCPE_OK;

    case MT_HREW:
        sim_debug(DEBUG_DETAIL, dptr, "Rewind unit=%d HS\n", unit);
        if ((uptr->u3 / ((uptr->flags & MTUF_LDN) ? 200 : 555) / 1200) > 2) {
           uptr->u3 -= (uptr->flags & MTUF_LDN) ? 1666 :4625;
           sim_activate(uptr, us_to_ticks(16000));
        } else {
           uptr->u5 &= ~(MT_CMDMSK);
           uptr->u5 |= MT_LREW;
           sim_activate(uptr, us_to_ticks(5000000));
        }
        return SCPE_OK;

    case MT_LREW:
        sim_debug(DEBUG_DETAIL, dptr, "Rewind unit=%d LS\n", unit);
        if (uptr->u3 > 0) {
           uptr->u3 -= (uptr->flags & MTUF_LDN) ? 373 :1036;
           sim_activate(uptr, us_to_ticks(16000));
           return SCPE_OK;
        } else {
           if(uptr->u5 & MT_UNLOAD)
               r = sim_tape_detach(uptr);
           else
               r = sim_tape_rewind(uptr);
           uptr->u5 &= ~(MT_CMDMSK|MT_UNLOAD);
           uptr->u5 |= MT_RDY;
           uptr->u3 = 0;
        }
        break;
    }
    return mt_error(uptr, chan, r, dptr);
}

/* Boot from given device */
t_stat
mt_boot(int32 unit_num, DEVICE * dptr)
{
    UNIT               *uptr = &dptr->units[unit_num];
    uint16              dev = unit_num + 020 + mt_dib.addr;
#if I7090 | I704 | I701
    t_mtrlnt            reclen;
    t_stat              r;
#endif

    if ((uptr->flags & UNIT_ATT) == 0)
        return SCPE_UNATT;      /* attached? */

    /* Start a read. */
    if (mt_cmd(dptr->units, IO_RDS, dev) != SCPE_OK)
        return STOP_IONRDY;

#if I7090 | I704 | I701
    r = sim_tape_rdrecf(uptr, &mt_buffer[GET_DEV_BUF(dptr->flags)][0], &reclen,
         BUFFSIZE);
    if (r != SCPE_OK)
        return r;
    uptr->u6 = 0;
    uptr->hwmark = reclen;

    /* Copy first three records. */
    mt_read_buff(uptr, MT_RDSB, dptr, &M[0]);
    mt_read_buff(uptr, MT_RDSB, dptr, &M[1]);
    if (UNIT_G_CHAN(uptr->flags) != 0)
        mt_read_buff(uptr, MT_RDSB, dptr, &M[2]);
    /* Make sure channel is set to start reading rest. */
#endif
    return chan_boot(unit_num, dptr);
}

void
mt_ini(UNIT * uptr, t_bool f)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);

    if (uptr->flags & UNIT_ATT)
        uptr->u5 = MT_RDY;
    else
        uptr->u5 = 0;
    uptr->u3 = 0;
    mt_chan[chan] = 0;
}

t_stat
mt_reset(DEVICE * dptr)
{
    return SCPE_OK;
}

t_stat
mt_tape_density(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
return sim_tape_set_dens(uptr, (val == MTUF_LDN) ? MT_DENS_200 : MT_DENS_556, NULL, NULL);
}

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

    if ((r = sim_tape_attach_ex(uptr, file, 0, 0)) != SCPE_OK)
        return r;
    sim_tape_set_dens(uptr, (uptr->flags == MTUF_LDN) ? MT_DENS_200 : MT_DENS_556, NULL, NULL);
    uptr->u3 = 0;
    uptr->u5 |= MT_RDY;
    uptr->flags |= MTUF_ONLINE;
    return SCPE_OK;
}

t_stat
mt_detach(UNIT * uptr)
{
    uptr->u3 = 0;
    uptr->u5 = 0;
    uptr->flags &= ~MTUF_ONLINE;
    return sim_tape_detach(uptr);
}

t_stat
mt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
   fprintf (st, "%s\n\n", mt_description(dptr));
   fprintf (st, "The magnetic tape controller assumes that all tapes are 7 track\n");
   fprintf (st, "with valid parity. Tapes are assumed to be 555.5 characters per\n");
   fprintf (st, "inch. To simulate a standard 2400foot tape, do:\n\n");
   fprintf (st, "   sim> SET %s LENGTH 15\n\n", dptr->name);
   fprintf (st, "The mag tape drives support the BOOT command\n\n");
   help_set_chan_type(st, dptr, "Mag tape");
   sim_tape_attach_help (st, dptr, uptr, flag, cptr);
   fprint_set_help(st, dptr);
   fprint_show_help(st, dptr);
   return SCPE_OK;
}

const char *
mt_description(DEVICE *dptr)
{
   return "IBM 729 Magnetic tape unit";
}

#endif
