/* sel32_mt.c: SEL-32 8051 Buffered Tape Processor

   Copyright (c) 2018-2023, James C. Bevier
   Portions provided by Richard Cornwell and other SIMH contributers

   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
   JAMES C. BEVIER 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. EOT is
   represented as 0xffffffff (-1) byte count.
*/

#include "sel32_defs.h"
#include "sim_tape.h"

#if NUM_DEVS_MT > 0

extern  uint32  SPAD[];                 /* cpu SPAD memory */

#define BUFFSIZE        (64 * 1024)
#define UNIT_MT         UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE

#define CMD      u3
/* BTP tape commands */
#define MT_INCH             0x00        /* Initialize channel command */
#define MT_WRITE            0x01        /* Write command */
#define MT_READ             0x02        /* Read command */
#define MT_NOP              0x03        /* Control command */
#define MT_SENSE            0x04        /* Sense command */
#define MT_RDBK             0x0c        /* Read Backward */
#define MT_RDCMP            0x13        /* Read and compare command */
#define MT_REW              0x23        /* Rewind command */
#define MT_RUN              0x33        /* Rewind and unload */
#define MT_FSR              0x43        /* Advance record */
#define MT_BSR              0x53        /* Backspace record */
#define MT_FSF              0x63        /* Advance to filemark  */
#define MT_BSF              0x73        /* Backspace to filemark */
#define MT_SETM             0x83        /* Set Mode command */
#define MT_WTM              0x93        /* Write Tape filemark */
#define MT_ERG              0xA3        /* Erase 3.5 of tape */
#define MT_MODEMSK          0xFF        /* Mode Mask */

/* set mode bits for BTP (MT_SETM) */
#define MT_MODE_AUTO        0x80        /* =0 Perform auto error recovery on read */
#define MT_MODE_FORCE       0x80        /* =1 Read regardless if error recovery fails */
#define MT_MDEN_800         0x40        /* =0 select 800 BPI NRZI mode 9 track only */
#define MT_MDEN_1600        0x40        /* =1 select 1600 BPI PE mode 9 track only */
#define MT_MDEN_6250        0x02        /* =0 Use mode from bit one for NRZI/PE */
#define MT_MDEN_6250        0x02        /* =1 6250 BPI GCR mode 9 track only */
#define MT_MDEN_SCATGR      0x01        /* =1 HSTP scatter/gather mode */
#define MT_MDEN_MSK         0x42        /* Density mask */

#define MT_CTL_MSK          0x38        /* Mask for control flags */
#define MT_CTL_NOP          0x00        /* Nop control mode */
#define MT_CTL_NRZI         0x08        /* 9 track 800 bpi mode */
#define MT_CTL_RST          0x10        /* Set density, odd, convert on, trans off */
#define MT_CTL_NOP2         0x18        /* 9 track 1600 NRZI mode */

/* in u3 is device command code and status */
#define MT_CMDMSK           0x00ff      /* Command being run */
#define MT_READDONE         0x0400      /* Read finished, end channel */
#define MT_MARK             0x0800      /* Sensed tape mark in move command */
#define MT_ODD              0x1000      /* Odd parity */
#define MT_TRANS            0x2000      /* Translation turned on ignored 9 track  */
#define MT_CONV             0x4000      /* Data converter on ignored 9 track  */
#define MT_BUSY             0x8000      /* Flag to send a CUE */

#define POS      u4
/* in u4 is current buffer position */

#define SNS      u5
/* in u5 packs sense byte 0, 1, 2 and 3 */
/* Sense byte 0 */
#define SNS_CMDREJ       0x80000000     /* Command reject */
#define SNS_INTVENT      0x40000000     /* Unit intervention required */
#define SNS_SPARE1       0x20000000     /* Spare */
#define SNS_EQUCHK       0x10000000     /* Equipment check */
#define SNS_DATCHK       0x08000000     /* Data Check */
#define SNS_OVRRUN       0x04000000     /* Data overrun */
#define SNS_SPARE2       0x02000000     /* Spare */
#define SNS_LOOKER       0x01000000     /* lookahead error */

/* Sense byte 1 */
#define SNS_PEMODER      0x800000       /* PE tape mode error */
#define SNS_TPECHK       0x400000       /* Tape PE mode check */
#define SNS_FMRKDT       0x200000       /* File mark detected EOF */
#define SNS_CORERR       0x100000       /* Corrected Error */
#define SNS_HARDER       0x080000       /* Hard Error */
#define SNS_MRLDER       0x040000       /* Mode register load error */
#define SNS_DATAWR       0x020000       /* Data written */
#define SNS_SPARE3       0x010000       /* Spare */

/* Sense byte 2 mode register bits */
#define SNS_MREG0        0x8000         /* 0 - Auto retry on read error */
                                        /* 1 - Ignore read errors */
#define SNS_MREG1        0x4000         /* 0 - NRZI */
                                        /* 1 - PE */
#define SNS_MREG2        0x2000         /* Mode register bit 2 N/U */
#define SNS_MREG3        0x1000         /* Mode register bit 3 N/U */
#define SNS_MREG4        0x0800         /* Mode register bit 4 N/U */
#define SNS_MREG5        0x0400         /* Mode register bit 5 N/U */
#define SNS_MREG6        0x0200         /* Mode register bit 6 N/U */
#define SNS_MREG7        0x0100         /* 1 - HSDP scatter/gather mode */

/* Sense byte 3 */
/* data returned for SENSE cmd (0x04) */
#define SNS_RDY          0x80           /* Drive Ready */
#define SNS_ONLN         0x40           /* Drive Online */
#define SNS_WRP          0x20           /* Drive is file protected (write ring missing) */
#define SNS_NRZI         0x10           /* Drive is NRZI */
#define SNS_SPARE4       0x08           /* Spare */
#define SNS_LOAD         0x04           /* Drive is at load point */
#define SNS_EOT          0x02           /* Drive is at EOT */
#define SNS_SPARE5       0x01           /* Spare */

#define SNS_BYTE4        0x00           /* Hardware errors not supported */
#define SNS_BYTE5        0x00           /* Hardware errors not supported */

#define MT_CONV1         0x40
#define MT_CONV2         0x80
#define MT_CONV3         0xc0

/* u6 holds the packed characters and unpack counter */
#define BUF_EMPTY(u)  (u->hwmark == 0xFFFFFFFF)
#define CLR_BUF(u)     u->hwmark =  0xFFFFFFFF

/* forward definitions */
t_stat      mt_preio(UNIT *uptr, uint16 chan);
t_stat      mt_startcmd(UNIT *uptr, uint16 chan,  uint8 cmd) ;
t_stat      mt_iocl(CHANP *chp, int32 tic_ok);
t_stat      mt_srv(UNIT *uptr);
t_stat      mt_boot(int32 unitnum, DEVICE *dptr);
void        mt_ini(UNIT *uptr, t_bool);
t_stat      mt_rschnlio(UNIT *uptr);
t_stat      mt_haltio(UNIT *uptr);
t_stat      mt_reset(DEVICE *dptr);
t_stat      mt_attach(UNIT *uptr, CONST char *);
t_stat      mt_detach(UNIT *uptr);
t_stat      mt_help(FILE *, DEVICE *dptr, UNIT *uptr, int32, const char *);
const char  *mt_description(DEVICE *);
extern      uint32  readfull(CHANP *chp, uint32 maddr, uint32 *word);
extern      uint16  loading;            /* set when doing IPL */
extern      int     irq_pend;           /* pending interrupt flag */
extern      uint32  cont_chan(uint16 chsa);

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

/* Gould Buffered Tape Processor (BTP) - Model 8051 */
/* Integrated channel controller */

/* Class F MT BTP I/O device status response in IOCD address pointer location */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */
/* |0 0 0 0|0 0 0 0|0 0 1 1|1 1 1 1|1 1 1 1|2 2 2 2|2 2 2 2|2 2 3 3| */
/* |0 1 2 3|4 5 6 7|8 9 0 1|2 3 4 5|6 7 8 9|0 1 2 3|4 5 6 7|8 9 3 1| */
/* | Cond  |0 0 0 0|         Address of status doubleword or zero  | */
/* | Code                                                          | */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */
/* */
/* Bits 0-3 - Condition codes */
/* 0000 - operation accepted will echo status not sent by the channel */
/* 0001 - channel busy */
/* 0010 - channel inop or undefined */
/* 0011 - subchannel busy */
/* 0100 - status stored */
/* 0101 - unsupported transaction */
/* 1000 - Operation accepted/queued, no echo status */

/* Status Doubleword */
/* Word 1 */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */
/* |0 0 0 0|0 0 0 0|0 0 1 1|1 1 1 1|1 1 1 1|2 2 2 2|2 2 2 2|2 2 3 3| */
/* |0 1 2 3|4 5 6 7|8 9 0 1|2 3 4 5|6 7 8 9|0 1 2 3|4 5 6 7|8 9 3 1| */
/* |Sub Address    |                24 bit IOCD address            | */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */
/* Word 2 */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */
/* |0 0 0 0|0 0 0 0|0 0 1 1|1 1 1 1|1 1 1 1|2 2 2 2|2 2 2 2|2 2 3 3| */
/* |0 1 2 3|4 5 6 7|8 9 0 1|2 3 4 5|6 7 8 9|0 1 2 3|4 5 6 7|8 9 3 1| */
/* |        16 bit of status       |      Residual Byte Count      | */
/* |-------+-------+-------+-------+-------+-------+-------+-------| */

/* Status Bits */
/* Bit 00 - ECHO    Halt I/O and Stop I/O function */
/* Bit 01 - PCI     Program Controlled Interrupt */
/* Bit 02 - IL      Incorrect Length */
/* Bit 03 - CPC     Channel Program Check */
/* Bit 04 - CDC     Channel Data Check */
/* Bit 05 - CCC     Channel Control Check */
/* Bit 06 - IC      Interface Check */
/* Bit 07 - CHC     Chaining Check */
/* Bit 08 - DB      Device Busy */
/* Bit 09 - SM      Status Modifier */
/* Bit 10 - CNTE    Controller End */
/* Bit 11 - ATTN    Attention */
/* Bit 12 - CE      Channel End */
/* Bit 13 - DE      Device End */
/* Bit 14 - UC      Unit Check */
/* Bit 15 - UE      Unit Exception */

/* 41 Word Main memory channel buffer provided by INCH command */
/* when software is initializing the channel */
/*  Word 01 - Status Doubleword 1 - Word 1 */
/*  Word 02 - Status Doubleword 1 - Word 2 */
/*  Word 03 - Status Doubleword 2 - Word 1 */
/*  Word 04 - Status Doubleword 2 - Word 2 */
/*  Word 05 - BTP Error Recovery IOCD Address */
/*  Word 06 - Queue Command List Doubleword - Word 1 */
/*  Word 07 - Queue Command List Doubleword - Word 2 */
/*  Word 08 - 16 bit Logical Q-pointer  | 16 bit Physical Q-pointer */
/*  Word 09 - 16 bit Active Retry Count | 16 bit Constant Retry Count */
/*  Word 10 - Accumulated Write Count - Drive 0 */
/*  Word 11 - Accumulated Read Count - Drive 0 */
/*  Word 12 - Write Error Count - Drive 0 */
/*  Word 13 - Read Error Count - Drive 0 */
/*  Word 14 - Accumulated Write Count - Drive 1 */
/*  Word 15 - Accumulated Read Count - Drive 1 */
/*  Word 16 - Write Error Count - Drive 1 */
/*  Word 17 - Read Error Count - Drive 1 */
/*  Word 18 - Accumulated Write Count - Drive 2 */
/*  Word 19 - Accumulated Read Count - Drive 2 */
/*  Word 20 - Write Error Count - Drive 2 */
/*  Word 21 - Read Error Count - Drive 2 */
/*  Word 22 - Accumulated Write Count - Drive 3 */
/*  Word 23 - Accumulated Read Count - Drive 3 */
/*  Word 24 - Write Error Count - Drive 3 */
/*  Word 25 - Read Error Count - Drive 3 */
/*  Word 26 - Accumulated Write Count - Drive 4 */
/*  Word 27 - Accumulated Read Count - Drive 4 */
/*  Word 28 - Write Error Count - Drive 4 */
/*  Word 29 - Read Error Count - Drive 4 */
/*  Word 30 - Accumulated Write Count - Drive 5 */
/*  Word 31 - Accumulated Read Count - Drive 5 */
/*  Word 32 - Write Error Count - Drive 5 */
/*  Word 33 - Read Error Count - Drive 5 */
/*  Word 34 - Accumulated Write Count - Drive 6 */
/*  Word 35 - Accumulated Read Count - Drive 6 */
/*  Word 36 - Write Error Count - Drive 6 */
/*  Word 37 - Read Error Count - Drive 6 */
/*  Word 38 - Accumulated Write Count - Drive 7 */
/*  Word 39 - Accumulated Read Count - Drive 7 */
/*  Word 40 - Write Error Count - Drive 7 */
/*  Word 41 - Read Error Count - Drive 7 */

int32               valid_dens = MT_800_VALID|MT_1600_VALID|MT_6250_VALID;
MTAB                mt_mod[] = {
    {MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL,
       "Write ring in place"},
    {MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL, NULL, NULL,
       "No write ring in place"},
    {MTAB_XTD|MTAB_VUN|MTAB_VALR, 0, "DENSITY", "DENSITY",
        &sim_tape_set_dens, &sim_tape_show_dens, &valid_dens,
       "Set tape density"},
    {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 | MTAB_VALR, 0, "DEV", "DEV", &set_dev_addr,
        &show_dev_addr, NULL, "Device address"},
    {0}
};

UNIT                mta_unit[] = {
    /* Unit data layout for MT devices */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1000)},       /* 0 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1001)},       /* 1 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1002)},       /* 2 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1003)},       /* 3 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1004)},       /* 4 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1005)},       /* 5 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1006)},       /* 6 */
    {UDATA(&mt_srv, UNIT_MT, 0), 0, UNIT_ADDR(0x1007)},       /* 7 */
};

/* channel program information */
CHANP           mta_chp[NUM_UNITS_MT] = {0};

DIB             mta_dib = {
    mt_preio,       /* t_stat (*pre_io)(UNIT *uptr, uint16 chan)*/  /* Pre Start I/O */
    mt_startcmd,    /* t_stat (*start_cmd)(UNIT *uptr, uint16 chan, uint8 cmd)*/ /* Start command */
    mt_haltio,      /* t_stat (*halt_io)(UNIT *uptr) */         /* Halt I/O */
    NULL,           /* t_stat (*stop_io)(UNIT *uptr) */         /* Stop I/O */
    NULL,           /* t_stat (*test_io)(UNIT *uptr) */         /* Test I/O */
    NULL,           /* t_stat (*rsctl_io)(UNIT *uptr) */        /* Reset Controller */
    mt_rschnlio,    /* t_stat (*rschnl_io)(UNIT *uptr) */       /* Reset Channel */
    NULL,           /* t_stat (*iocl_io)(CHANP *chp, int32 tic_ok)) */  /* Process IOCL */
    mt_ini,         /* void  (*dev_ini)(UNIT *, t_bool) */      /* init function */
    mta_unit,       /* UNIT* units */                           /* Pointer to units structure */
    mta_chp,        /* CHANP* chan_prg */                       /* Pointer to chan_prg structure */
    NULL,           /* IOCLQ *ioclq_ptr */                      /* IOCL entries, 1 per UNIT */
    NUM_UNITS_MT,   /* uint8 numunits */                        /* number of units defined */
    0x07,           /* uint8 mask */                            /* 8 devices - device mask */
    0x1000,         /* uint16 chan_addr */                      /* parent channel address */
    0,              /* uint32 chan_fifo_in */                   /* fifo input index */
    0,              /* uint32 chan_fifo_out */                  /* fifo output index */
    {0}             /* uint32 chan_fifo[FIFO_SIZE] */           /* interrupt status fifo for channel */
};

DEVICE          mta_dev = {
    "MTA", mta_unit, NULL, mt_mod,
    NUM_UNITS_MT, 16, 24, 4, 16, 32,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    /* ctxt is the DIB pointer */
    &mta_dib, DEV_BUF_NUM(0)|DEV_DIS|DEV_DISABLE|DEV_DEBUG|DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description

};

#if NUM_DEVS_MT > 1
/* channel program information */
CHANP           mtb_chp[NUM_UNITS_MT] = {0};

UNIT            mtb_unit[] = {
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1800)},       /* 0 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1801)},       /* 1 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1802)},       /* 2 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1803)},       /* 3 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1804)},       /* 4 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1805)},       /* 5 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1806)},       /* 6 */
    {UDATA(&mt_srv, UNIT_MT|0), 0, UNIT_ADDR(0x1807)},       /* 7 */
};

/* device information block */
DIB             mtb_dib = {
    mt_preio,       /* t_stat (*pre_io)(UNIT *uptr, uint16 chan)*/  /* Pre Start I/O */
    mt_startcmd,    /* t_stat (*start_cmd)(UNIT *uptr, uint16 chan, uint8 cmd)*/ /* Start command */
    mt_haltio,      /* t_stat (*halt_io)(UNIT *uptr) */         /* Halt I/O */
    NULL,           /* t_stat (*stop_io)(UNIT *uptr) */         /* Stop I/O */
    NULL,           /* t_stat (*test_io)(UNIT *uptr) */         /* Test I/O */
    NULL,           /* t_stat (*rsctl_io)(UNIT *uptr) */        /* Reset Controller */
    mt_rschnlio,    /* t_stat (*rschnl_io)(UNIT *uptr) */       /* Reset Channel */
    NULL,           /* t_stat (*iocl_io)(CHANP *chp, int32 tic_ok)) */  /* Process IOCL */
    mt_ini,         /* void  (*dev_ini)(UNIT *, t_bool) */      /* init function */
    mtb_unit,       /* UNIT* units */                           /* Pointer to units structure */
    mtb_chp,        /* CHANP* chan_prg */                       /* Pointer to chan_prg structure */
    NULL,           /* IOCLQ *ioclq_ptr */                      /* IOCL entries, 1 per UNIT */
    NUM_UNITS_MT,   /* uint8 numunits */                        /* number of units defined */
    0x07,           /* uint8 mask */                            /* 8 devices - device mask */
    0x1800,         /* uint16 chan_addr */                      /* parent channel address */
    0,              /* uint32 chan_fifo_in */                   /* fifo input index */
    0,              /* uint32 chan_fifo_out */                  /* fifo output index */
    {0}             /* uint32 chan_fifo[FIFO_SIZE] */           /* interrupt status fifo for channel */
};

DEVICE          mtb_dev = {
    "MTB", mtb_unit, NULL, mt_mod,
    NUM_UNITS_MT, 8, 15, 1, 8, 8,
    NULL, NULL, &mt_reset, &mt_boot, &mt_attach, &mt_detach,
    &mtb_dib, DEV_BUF_NUM(0)|DEV_DIS|DEV_DISABLE|DEV_DEBUG|DEV_TAPE, 0, dev_debug,
    NULL, NULL, &mt_help, NULL, NULL, &mt_description
};
#endif

/* table with filenames of attached units */
struct attfileinfo {
    uint16 chsa;
    char *att_file;
    struct attfileinfo *next;
};

struct attfileinfo *att_files = (struct attfileinfo *) NULL;

/* load in the IOCD and process the commands */
/* return = 0 OK */
/* return = 1 error, chan_status will have reason */
t_stat  mt_iocl(CHANP *chp, int32 tic_ok)
{
    uint32      word1 = 0;
    uint32      word2 = 0;
    int32       docmd = 0;
    UNIT        *uptr = chp->unitptr;           /* get the unit ptr */
    uint16      chan = get_chan(chp->chan_dev); /* our channel */
    uint16      chsa = chp->chan_dev;
    uint16      devstat = 0;
    DEVICE      *dptr = get_dev(uptr);
    DIB         *pdibp = dib_chan[get_chan(chsa)];   /* channel DIB */
    CHANP       *pchp = pdibp->chan_prg;        /* get channel chp */

    /* check for valid iocd address if 1st iocd */
    if (chp->chan_info & INFO_SIOCD) {          /* see if 1st IOCD in channel prog */
        if (chp->chan_caw & 0x3) {              /* must be word bounded */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl iocd bad address chsa %02x caw %06x\n",
                chsa, chp->chan_caw);
            chp->ccw_addr = chp->chan_caw;      /* set the bad iocl address */
            chp->chan_status |= STATUS_PCHK;    /* program check for invalid iocd addr */
            return 1;                           /* error return */
        }
    }
loop:
    sim_debug(DEBUG_EXP, dptr,
        "mt_iocl @%06x @loop chan_status[%04x] %04x SNS %08x\n",
        chp->chan_caw, chan, chp->chan_status, uptr->SNS);

    /* Abort if we have any errors */
    if (chp->chan_status & STATUS_ERROR) {      /* check channel error status */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl ERROR1 chan_status[%04x] %04x\n", chan, chp->chan_status);
        return 1;                               /* return error */
    }

    /* Read in first CCW */
    if (readfull(chp, chp->chan_caw, &word1) != 0) { /* read word1 from memory */
        chp->chan_status |= STATUS_PCHK;        /* memory read error, program check */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl ERROR2 chan_status[%04x] %04x\n", chan, chp->chan_status);
        return 1;                               /* error return */
    }

    /* Read in second CCW */
    if (readfull(chp, chp->chan_caw+4, &word2) != 0) { /* read word2 from memory */
        chp->chan_status |= STATUS_PCHK;        /* memory read error, program check */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl ERROR3 chan_status[%04x] %04x\n", chan, chp->chan_status);
        return 1;                               /* error return */
    }

    sim_debug(DEBUG_CMD, dptr,
        "mt_iocl @%06x read ccw chsa %04x IOCD wd 1 %08x wd 2 %08x SNS %08x\n",
        chp->chan_caw, chp->chan_dev, word1, word2, uptr->SNS);

    chp->chan_caw = (chp->chan_caw & 0xfffffc) + 8; /* point to next IOCD */

    /* Check if we had data chaining in previous iocd */
    /* if we did, use previous cmd value */
    if (((chp->chan_info & INFO_SIOCD) == 0) && /* see if 1st IOCD in channel prog */
       (chp->ccw_flags & FLAG_DC)) {            /* last IOCD have DC set? */
        sim_debug(DEBUG_CMD, dptr,
            "mt_iocl @%06x DO DC, ccw_flags %04x cmd %02x\n",
            chp->chan_caw, chp->ccw_flags, chp->ccw_cmd);
    } else
        chp->ccw_cmd = (word1 >> 24) & 0xff;    /* set new command from IOCD wd 1 */

    if (!MEM_ADDR_OK(word1 & MASK24)) {         /* see if memory address invalid */
        chp->chan_status |= STATUS_PCHK;        /* bad, program check */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl mem error PCHK chan_status[%04x] %04x addr %08x\n",
            chan, chp->chan_status, word1 & MASK24);
        return 1;                               /* error return */
    }

    chp->ccw_count = word2 & 0xffff;            /* get 16 bit byte count from IOCD WD 2*/

    /* validate the commands for the mt */
    switch (chp->ccw_cmd) {
    case MT_WRITE: case MT_READ: case MT_NOP: case MT_SENSE:
    case MT_RDBK: case MT_RDCMP: case MT_REW: case MT_RUN: case MT_FSR:
    case MT_BSR: case MT_FSF: case MT_BSF: case MT_SETM: case MT_WTM: case MT_ERG:  
        /* the inch command must be first command issued */
        if ((!loading) && (pchp->chan_inch_addr == 0)) {
            chp->chan_status |= STATUS_PCHK;    /* program check for invalid cmd */
            uptr->SNS |= SNS_CMDREJ;            /* cmd rejected */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl bad cmd %02x chan_status[%04x] %04x\n",
                chp->ccw_cmd, chan, chp->chan_status);
            return 1;                           /* error return */
        }
    case MT_INCH:
        break;
    default:
        chp->chan_status |= STATUS_PCHK;        /* program check for invalid cmd */
        uptr->SNS |= SNS_CMDREJ;                /* cmd rejected */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl bad cmd %02x chan_status[%04x] %04x\n",
            chp->ccw_cmd, chan, chp->chan_status);
        return 1;                               /* error return */
    }

    if (chp->chan_info & INFO_SIOCD) {          /* see if 1st IOCD in channel prog */
        /* 1st command can not be a TIC */
        if (chp->ccw_cmd == CMD_TIC) {
            chp->chan_status |= STATUS_PCHK;    /* program check for invalid tic */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl TIC bad cmd chan_status[%04x] %04x\n",
                chan, chp->chan_status);
            return 1;                           /* error return */
        }
    }

    /* TIC can't follow TIC or be first in command chain */
    /* diags send bad commands for testing.  Use all of op */
    if (chp->ccw_cmd == CMD_TIC) {
        if (tic_ok) {
            if (((word1 & MASK24) == 0) || (word1 & 0x3)) {
                sim_debug(DEBUG_EXP, dptr,
                    "mt_iocl tic cmd bad address chan %02x tic caw %06x IOCD wd 1 %08x\n",
                    chan, chp->chan_caw, word1);
                chp->chan_status |= STATUS_PCHK; /* program check for invalid tic */
                chp->chan_caw = word1 & MASK24; /* get new IOCD address */
                uptr->SNS |= SNS_CMDREJ;        /* cmd rejected status */
                return 1;                       /* error return */
            }
            tic_ok = 0;                         /* another tic not allowed */
            chp->chan_caw = word1 & MASK24;     /* get new IOCD address */
            sim_debug(DEBUG_CMD, dptr,
                "mt_iocl tic cmd ccw chan %02x tic caw %06x IOCD wd 1 %08x\n",
                chan, chp->chan_caw, word1);
            goto loop;                          /* restart the IOCD processing */
        }
        chp->chan_caw = word1 & MASK24;         /* get new IOCD address */
        chp->chan_status |= STATUS_PCHK;        /* program check for invalid tic */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl TIC ERROR chan_status[%04x] %04x\n", chan, chp->chan_status);
        return 1;                               /* error return */
    }

    /* Check if we had data chaining in previous iocd */
    if ((chp->chan_info & INFO_SIOCD) ||        /* see if 1st IOCD in channel prog */
        (((chp->chan_info & INFO_SIOCD) == 0) && /* see if 1st IOCD in channel prog */
        ((chp->ccw_flags & FLAG_DC) == 0))) {    /* last IOCD have DC set? */
        sim_debug(DEBUG_CMD, dptr,
            "mt_iocl @%06x DO CMD No DC, ccw_flags %04x cmd %02x\n",
            chp->chan_caw, chp->ccw_flags, chp->ccw_cmd);
        docmd = 1;                              /* show we have a command */
    }

    /* Set up for this command */
    chp->ccw_flags = (word2 >> 16) & 0xfc00;    /* get flags from bits 0-4 of WD 2 of IOCD */
    chp->chan_status = 0;                       /* clear status for next IOCD */
    /* make a 24 bit address */
    chp->ccw_addr = word1 & MASK24;             /* set the data/seek address */

    if (chp->ccw_flags & FLAG_PCI) {            /* do we have prog controlled int? */
        chp->chan_status |= STATUS_PCI;         /* set PCI flag in status */
        irq_pend = 1;                           /* interrupt pending */
    }

    /* validate parts of IOCD2 that are reserved */    
    if (word2 & 0x07ff0000) {                   /* bits 5-15 must be zero */
        chp->chan_status |= STATUS_PCHK;        /* program check for invalid iocd */
        sim_debug(DEBUG_EXP, dptr,
            "mt_iocl IOCD2 chan_status[%04x] %04x\n", chan, chp->chan_status);
        return 1;                               /* error return */
    }

    /* DC can only be used with a read/write cmd */
    if (chp->ccw_flags & FLAG_DC) {
        if ((chp->ccw_cmd != MT_READ) && (chp->ccw_cmd != MT_WRITE) &&
            (chp->ccw_cmd != MT_RDBK)) {
            chp->chan_status |= STATUS_PCHK;    /* program check for invalid DC */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl DC ERROR chan_status[%04x] %04x\n", chan, chp->chan_status);
            return 1;                           /* error return */
        }
    }

    chp->chan_byte = BUFF_BUSY;                 /* busy & no bytes transferred yet */

    sim_debug(DEBUG_XIO, dptr,
        "mt_iocl @%06x read docmd %01x addr %06x count %04x chan %04x ccw_flags %04x\n",
        chp->chan_caw, docmd, chp->ccw_addr, chp->ccw_count, chan, chp->ccw_flags);

    if (docmd) {                                /* see if we need to process a command */
        DIB *dibp = dib_unit[chp->chan_dev];    /* get the DIB pointer */
 
        uptr = chp->unitptr;                    /* get the unit ptr */
        if (dibp == 0 || uptr == 0) {
            chp->chan_status |= STATUS_PCHK;    /* program check if it is */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl bad dibp or uptr chan_status[%04x] %04x\n", chan, chp->chan_status);
            return 1;                           /* if none, error */
        }

        sim_debug(DEBUG_XIO, dptr,
            "mt_iocl @%06x before start_cmd chan %04x status %04x count %04x SNS %08x\n",
            chp->chan_caw, chan, chp->chan_status, chp->ccw_count, uptr->SNS);

        /* call the device startcmd function to process the current command */
        /* just replace device status bits */
        chp->chan_info &= ~INFO_CEND;           /* show chan_end not called yet */
        devstat = dibp->start_cmd(uptr, chan, chp->ccw_cmd);
        chp->chan_status = (chp->chan_status & 0xff00) | devstat;
        chp->chan_info &= ~INFO_SIOCD;          /* show not first IOCD in channel prog */

        sim_debug(DEBUG_XIO, dptr,
            "mt_iocl @%06x after start_cmd chsa %04x status %08x count %04x SNS %08x\n",
            chp->chan_caw, chsa, chp->chan_status, chp->ccw_count, uptr->SNS);

        /* see if bad status */
        if (chp->chan_status & (STATUS_ATTN|STATUS_ERROR)) {
            chp->chan_status |= STATUS_CEND;    /* channel end status */
            chp->ccw_flags = 0;                 /* no flags */
            chp->chan_byte = BUFF_NEXT;         /* have main pick us up */
            sim_debug(DEBUG_EXP, dptr,
                "mt_iocl bad status chsa %04x status %04x cmd %02x\n",
                chsa, chp->chan_status, chp->ccw_cmd);
            /* done with command */
            sim_debug(DEBUG_EXP, &cpu_dev,
                "mt_iocl ERROR return chsa %04x status %08x\n",
                chp->chan_dev, chp->chan_status);
            return 1;                           /* error return */
        }
        /* NOTE this code needed for MPX 1.X to run! */
        /* see if command completed */
        /* we have good status */
        if (chp->chan_status & (STATUS_DEND|STATUS_CEND)) {
            uint16  chsa = GET_UADDR(uptr->u3); /* get channel & sub address */
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* show I/O complete */
            sim_debug(DEBUG_XIO, dptr,
                "mt_iocl @%06x FIFO #%1x cmd complete chan %04x status %04x count %04x\n",
                chp->chan_caw, FIFO_Num(chsa), chan, chp->chan_status, chp->ccw_count);
        }
    }
    /* the device processor returned OK (0), so wait for I/O to complete */
    /* nothing happening, so return */
    sim_debug(DEBUG_XIO, dptr,
        "mt_iocl @%06x return, chsa %04x status %04x count %04x\n",
        chp->chan_caw, chsa, chp->chan_status, chp->ccw_count);
    return 0;                                   /* good return */
}

/* start a tape operation */
t_stat mt_preio(UNIT *uptr, uint16 chan) {
    DEVICE      *dptr = get_dev(uptr);
    int         unit = (uptr - dptr->units);
    uint16      chsa = GET_UADDR(uptr->CMD);
    DIB         *pdibp = dib_chan[get_chan(chsa)];   /* channel DIB */
    CHANP       *pchp = pdibp->chan_prg;        /* get channel chp */

    sim_debug(DEBUG_CMD, dptr, "mt_preio CMD %08x unit %02x chsa %04x incha %08x\n",
        uptr->CMD, unit, chsa, pchp->chan_inch_addr);
    if ((!loading) && (pchp->chan_inch_addr == 0)) {
        sim_debug(DEBUG_CMD, dptr,
            "mt_preio unit %02x chsa %04x NO INCH\n", unit, chsa);
        /* no INCH yet, so do nothing */
        return SNS_CTLEND;
    }
    if ((uptr->CMD & MT_CMDMSK) != 0) {             /* just return if busy */
        sim_debug(DEBUG_CMD, dptr,
            "mt_preio unit %02x chsa %04x BUSY\n", unit, chsa);
        return SNS_BSY;
    }
    if ((uptr->flags & UNIT_ATT) == 0) {            /* unit attached status */
        /* set status */
        uptr->SNS |= SNS_INTVENT;                   /* unit intervention required */
        uptr->SNS &= ~(SNS_RDY|SNS_ONLN);           /* unit not online or rdy */
        uptr->SNS &= ~SNS_LOAD;                     /* reset BOT detected */
        return SCPE_OK;                             /* good to go */
    }

    sim_debug(DEBUG_CMD, dptr, "mt_preio unit %02x chsa %04x OK\n", unit, chsa);
    return SCPE_OK;                                 /* good to go */
}

/* start an I/O operation */
t_stat mt_startcmd(UNIT *uptr, uint16 chan,  uint8 cmd)
{
    uint16      chsa = GET_UADDR(uptr->CMD);
    DEVICE      *dptr = get_dev(uptr);
    int         unit = (uptr - dptr->units);
    CHANP       *chp = find_chanp_ptr(chsa);    /* find the chanp pointer */

    sim_debug(DEBUG_EXP, dptr, "mt_startcmd entry chan %04x cmd %02x\n", chan, cmd);
    if (mt_busy[GET_DEV_BUF(dptr->flags)] != 0 || (uptr->CMD & MT_CMDMSK) != 0) {
        sim_debug(DEBUG_EXP, dptr, "mt_startcmd busy %02x chan %04x flags %08x CMD %02x\n",
            mt_busy[GET_DEV_BUF(dptr->flags)], chan, dptr->flags, uptr->CMD);
        uptr->flags |= MT_BUSY;             /* Flag we need to send CUE */
        return SNS_BSY;
    }

    sim_debug(DEBUG_EXP, dptr, "mt_startcmd processing unit %01x cmd %02x\n", unit, cmd);

    switch (cmd & 0xFF) {
    case 0x00:                              /* INCH command */
        sim_debug(DEBUG_CMD, dptr, "start INCH command\n");

        sim_debug(DEBUG_CMD, dptr,
            "mt_startcmd starting INCH cmd, chsa %04x MemBuf %08x cnt %04x\n",
            chsa, chp->ccw_addr, chp->ccw_count);

        /* UTX_needs_interrupt */
        cmd = MT_CMDMSK;                    /* insert INCH cmd as 0xff */
        /* fall through */
    case 0x03:                              /* Tape motion commands or NOP */
    case 0x13:                              /* Read and compare command */
    case 0x23:                              /* Rewind command */
    case 0x33:                              /* Rewind and unload */
    case 0x43:                              /* Advance record */
    case 0x53:                              /* Backspace record */
    case 0x63:                              /* Advance filemark  */
    case 0x73:                              /* Backspace filemark */
    case 0x83:                              /* Set Mode command */
    case 0x93:                              /* Write Tape filemark */
    case 0xA3:                              /* Erase 3.5 of tape */
        /* UTX_needs_interrupt on NOP or INCH */
        /* fall through */
    case 0x01:                              /* Write command */
    case 0x02:                              /* Read command */
    case 0x0C:                              /* Read backward */
        if (cmd == 0x01)
        sim_debug(DEBUG_EXP, dptr,
            "mt_startcmd WRITE chan %04x addr %06x cnt %04x\n",
            chan, chp->ccw_addr, chp->ccw_count);
        if (cmd == 0x02)
        sim_debug(DEBUG_EXP, dptr,
            "mt_startcmd READ chan %04x addr %06x cnt %04x\n",
            chan, chp->ccw_addr, chp->ccw_count);
        if (cmd != 0x03) {                  /* if this is a nop do not zero status */
            uptr->SNS = (uptr->SNS & 0x0000ff00);   /* clear all but byte 2 */
        }
        if ((uptr->flags & UNIT_ATT) == 0) {    /* unit attached status */
            uptr->SNS |= SNS_INTVENT;       /* unit intervention required */
            uptr->SNS &= ~(SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
            uptr->SNS &= ~SNS_LOAD;         /* reset BOT detected */
        sim_debug(DEBUG_CMD, dptr, "mt_startcmd detached sense %08x chan %04x cmd %02x\n",
            uptr->SNS, chan, cmd);
        } else {
            uptr->SNS |= (SNS_RDY|SNS_ONLN);    /* set ready status */
            if (sim_tape_wrp(uptr))
                uptr->SNS |= (SNS_WRP);     /* write protected */
            if (sim_tape_bot(uptr))
                uptr->SNS |= (SNS_LOAD);    /* tape at load point */
            if (sim_tape_eot(uptr))
                uptr->SNS |= (SNS_EOT);     /* tape at EOM */
        sim_debug(DEBUG_CMD, dptr, "mt_startcmd attached sense %08x chan %04x cmd %02x\n",
            uptr->SNS, chan, cmd);
        }
        /* Fall through */

    case 0x04:             /* Sense */
        uptr->CMD &= ~(MT_CMDMSK);          /* clear out last cmd */
        uptr->CMD |= cmd & MT_CMDMSK;       /* insert new cmd */
        CLR_BUF(uptr);                      /* buffer is empty */
        uptr->POS = 0;                      /* reset buffer position pointer */
        mt_busy[GET_DEV_BUF(dptr->flags)] = 1;  /* show we are busy */
        sim_debug(DEBUG_EXP, dptr, "mt_startcmd sense %08x return OK chan %04x cmd %02x\n",
            uptr->SNS, chan, cmd);
        sim_activate(uptr, 20);             /* Start unit off */
        return SCPE_OK;                     /* good to go */

    default:                                /* invalid command */
        sim_debug(DEBUG_EXP, dptr, "mt_startcmd CMDREJ return chan %04x cmd %02x\n",
            chan, cmd);
        uptr->SNS |= SNS_CMDREJ;
        /* send program check */
        return SNS_CHNEND|SNS_DEVEND|STATUS_PCHK;   /* add DEVEND 08/16/20 */
        break;
    }
}

/* Map simH errors into machine errors */
t_stat mt_error(UNIT *uptr, uint16 chsa, t_stat r, DEVICE *dptr)
{
    sim_debug(DEBUG_CMD, dptr, "mt_error status %08x\n", r);
    mt_busy[GET_DEV_BUF(dptr->flags)] &= ~1;    /* not busy anymore */

    switch (r) {                            /* switch on return value */
    case MTSE_OK:                           /* no error */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done with command */
        break;

    case MTSE_TMK:                          /* tape mark */
        sim_debug(DEBUG_CMD, dptr, "FILE MARK\n");
        uptr->SNS |= SNS_FMRKDT;            /* file mark detected */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
        break;

    case MTSE_WRP:                          /* write protected */
        uptr->SNS |= SNS_WRP;               /* write protected */
        sim_debug(DEBUG_CMD, dptr, "WRITE PROTECT %08x\n", r); /* operator intervention */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done with command */
        break;

    case MTSE_UNATT:                        /* unattached */
        uptr->SNS |= SNS_INTVENT;           /* unit intervention required */
        uptr->SNS &= ~(SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
        uptr->SNS &= ~SNS_LOAD;             /* reset BOT detected */
        sim_debug(DEBUG_CMD, dptr, "ATTENTION %08x\n", r); /* operator intervention */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
        break;

    case MTSE_IOERR:                        /* IO error */
    case MTSE_FMT:                          /* invalid format */
    case MTSE_RECE:                         /* error in record */
        sim_debug(DEBUG_CMD, dptr, "ERROR %08x\n", r);
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done with command */
        break;

    case MTSE_BOT:                          /* beginning of tape */
        uptr->SNS |= SNS_LOAD;              /* tape at BOT */
        sim_debug(DEBUG_CMD, dptr, "BOT\n");
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
        break;

    case MTSE_INVRL:                        /* invalid rec lnt */
    case MTSE_EOM:                          /* end of medium */
        uptr->SNS |= SNS_EOT;               /* tape at EOT */
        sim_debug(DEBUG_CMD, dptr, "EOT\n");
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
        break;
    }
    return SCPE_OK;
}

/* Handle processing of tape requests. */
t_stat mt_srv(UNIT *uptr)
{
    uint16      chsa = GET_UADDR(uptr->CMD);
    DEVICE      *dptr = get_dev(uptr);
    int         unit = (uptr - dptr->units);
    int         cmd = uptr->CMD & MT_CMDMSK;
    int         bufnum = GET_DEV_BUF(dptr->flags);
    CHANP       *chp = find_chanp_ptr(chsa);    /* find the chanp pointer */
    t_mtrlnt    reclen;
    t_stat      r = SCPE_ARG;               /* Force error if not set */
    int         i;
    char        *bufp;
    uint32      mema, m, skip;
    uint16      len;
    uint8       ch;
    struct attfileinfo *afi;

    sim_debug(DEBUG_CMD, dptr,
        "mt_srv unit %02x cmd %02x POS %x hwmark %03x\n",
        unit, cmd, uptr->POS, uptr->hwmark);

    /* reattach tapedev that has been set offline */
    if ((uptr->SNS & SNS_ONLN) == 0) {
        afi = att_files;
        while (afi != (struct attfileinfo *) NULL && afi->chsa != chsa)
            afi = afi->next;
        /* only when valid entry found */
        if (afi != (struct attfileinfo *) NULL && afi->att_file[0] != '\0')
            mt_attach(uptr, afi->att_file);
    }

    switch (cmd) {
    case MT_CMDMSK:   /* 0x0ff for inch 0x00 */ /* INCH is for channel, nothing for us */
        len = chp->ccw_count;               /* INCH command count */
        mema = chp->ccw_addr;               /* get inch or buffer addr */
        sim_debug(DEBUG_CMD, dptr,
            "mt_srv starting INCH %06x cmd, chsa %04x MemBuf %06x cnt %04x\n",
            mema, chsa, chp->ccw_addr, chp->ccw_count);

        if (len == 0) {
            /* we have invalid count, error, bail out */
            uptr->CMD &= ~(0xffff);         /* remove old status bits & cmd */
            uptr->SNS |= SNS_CMDREJ|SNS_EQUCHK;
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
            return SCPE_OK;
        }

        /* the chp->ccw_addr location contains the inch address */
        /* call set_inch() to setup inch buffer */
        /* 4 wd buffer is provided for 2 status dbl words */
        i = set_inch(uptr, mema, 2);        /* new address of 33 entries */

        if ((i == SCPE_MEM) || (i == SCPE_ARG)) {   /* any error */
            /* we have error, bail out */
            uptr->CMD &= ~(0xffff);         /* remove old status bits & cmd */
            uptr->SNS |= SNS_CMDREJ|SNS_EQUCHK;
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
            return SCPE_OK;
        }
        /* set halfwords 16 & 17 to 5 as default retry count in inch data */
        /* UTX uses this value to see if the device is a buffered tape processor */
        /* they must be non-zero and equal to be BTP */
        WMH(mema+(16<<1),5);                /* write left HW with count */
        WMH(mema+(17<<1),5);                /* write right HW with count */
        sim_debug(DEBUG_CMD, dptr,
            "mt_srv cmd INCH chsa %04x chsa %06x count %04x completed word 16 %08x\n",
            chsa, mema, chp->ccw_count, RMW(mema+(8<<2)));
        uptr->CMD &= ~MT_CMDMSK;            /* clear the cmd */
        mt_busy[bufnum] &= ~1;              /* make our buffer not busy */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
        return SCPE_OK;

    case 0x80:      /* other? */            /* default to NOP */
        sim_debug(DEBUG_CMD, dptr, "mt_srv cmd 80 DIAG unit=%04x SNS %08x\n", unit, uptr->SNS);
        ch = (uptr->SNS >> 24) & 0xff;      /* get sense byte 0 status */
        sim_debug(DEBUG_CMD, dptr, "sense unit %02x byte 0 %02x\n", unit, ch);
        chan_write_byte(chsa, &ch);         /* write byte 0 */
        ch = (uptr->SNS >> 16) & 0xff;      /* get sense byte 1 status */
        sim_debug(DEBUG_CMD, dptr, "sense unit %02x byte 1 %02x\n", unit, ch);
        chan_write_byte(chsa, &ch);         /* write byte 1 */
        ch = (uptr->SNS >> 8) & 0xff;       /* get sense byte 2 status */
        sim_debug(DEBUG_CMD, dptr, "sense unit %02x byte 2 %02x\n", unit, ch);
        chan_write_byte(chsa, &ch);         /* write byte 2 */
        ch = (uptr->SNS >> 0) & 0xff;       /* get sense byte 3 status */
        sim_debug(DEBUG_CMD, dptr, "sense unit %02x byte 3 %02x\n", unit, ch);
        chan_write_byte(chsa, &ch);         /* write byte 3 */
        /* write zero extra status */
        for (ch=4; ch < 0xc; ch++) {
            uint8   zc = 0;
            chan_write_byte(chsa, &zc);     /* write zero byte */
            sim_debug(DEBUG_CMD, dptr,
                "sense unit %02x byte %1x %02x\n", unit, ch, zc);
        }
        uptr->CMD &= ~MT_CMDMSK;            /* clear the cmd */
        mt_busy[bufnum] &= ~1;              /* make our buffer not busy */
        uptr->SNS = (uptr->SNS & 0x0000ff00);   /* clear all but byte 2 */
        if ((uptr->flags & UNIT_ATT) == 0)  /* unit attached status */
            uptr->SNS |= SNS_INTVENT;       /* unit intervention required */
        else
            uptr->SNS &= ~(SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
        sim_debug(DEBUG_CMD, dptr, "mt_srv DIAG SNS %08x char complete unit=%02x\n",
            uptr->SNS, unit);
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
        return SCPE_OK;

    case MT_NOP:    /* 0x03 */              /* NOP motion command */
        uptr->CMD &= ~MT_CMDMSK;            /* clear the cmd */
        mt_busy[bufnum] &= ~1;              /* make our buffer not busy */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
        return SCPE_OK;

    case MT_SENSE:  /* 0x04 */              /* get sense data */
        /* write requested status */
        len = chp->ccw_count;               /* command count */
        for (i=0; i<4; i++) {
            ch = 0;
            if (i<4)
                ch = (uptr->SNS >> (24-(i*8))) & 0xff;  /* get 8 bits of status */
            chan_write_byte(chsa, &ch);     /* write zero byte */
            sim_debug(DEBUG_CMD, dptr,
                "sense unit %02x byte %1x %02x\n", unit, i, ch);
        }
        uptr->CMD &= ~MT_CMDMSK;            /* clear the cmd */
        mt_busy[bufnum] &= ~1;              /* make our buffer not busy */
        uptr->SNS = (uptr->SNS & 0x0000ff00);   /* clear all but byte 2 */
        if (!(uptr->flags & UNIT_ATT))      /* unit attached status */
            uptr->SNS |= SNS_INTVENT;       /* unit intervention required */
        else
            uptr->SNS |= (SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
        sim_debug(DEBUG_CMD, dptr, "mt_srv SENSE %08x char complete unit=%02x\n",
            uptr->SNS, unit);
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
        return SCPE_OK;

    case MT_SETM:   /* 0x83 */              /* set mode byte */
        sim_debug(DEBUG_CMD, dptr, "mt_srv cmd 0x83 SETM unit=%02x\n", unit);
        /* Grab data until channel has no more */
        if (chan_read_byte(chsa, &ch)) {
            if (uptr->POS > 0) {            /* Only if data in record */
                reclen = uptr->hwmark;      /* set record length */
                ch = mt_buffer[bufnum][0];  /* get the first byte read */
                sim_debug(DEBUG_CMD, dptr,
                    "Write mode data done unit %02x chars %02x mode %02x\n", unit, reclen, ch);
                /* put mode bits into byte 2 of SNS */
                uptr->SNS = (uptr->SNS & 0xffff00ff) | (ch << 8);
                uptr->POS = 0;              /* no bytes anymore */
                uptr->CMD &= ~MT_CMDMSK;    /* no cmd to do */
                mt_busy[bufnum] &= ~1;      /* set not busy */
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* return end status */
            }
        } else {
            mt_buffer[bufnum][uptr->POS++] = ch;    /* save the character read in */
            sim_debug(DEBUG_CMD, dptr, "Write mode data in unit %02x POS %04x mode %02x\n",
                  unit, uptr->POS, ch);
            uptr->hwmark = uptr->POS;       /* set high water mark */
            sim_activate(uptr, 30);         /* wait time */
        }
        return SCPE_OK;
    default:
        break;
    }

    /* only run these commands if we have a tape attached */
    if ((uptr->flags & UNIT_ATT) == 0) {    /* unit attached status */
        uptr->SNS |= SNS_INTVENT;           /* unit intervention required */
        uptr->SNS &= ~(SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
        uptr->SNS &= ~SNS_LOAD;             /* reset BOT detected */
        mt_busy[bufnum] &= ~1;              /* make our buffer not busy */
        /* we are completed with unit check status */
        uptr->CMD &= ~MT_CMDMSK;            /* clear the cmd */
        chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
        return SCPE_OK;
    }

    switch (cmd) {
    case MT_READ:   /* 0x02 */              /* read a record from the device */
reread:
        if (uptr->CMD & MT_READDONE) {      /* is the read complete */
            uptr->SNS &= ~(SNS_LOAD|SNS_EOT);   /* reset BOT & EOT */
            if (sim_tape_eot(uptr)) {       /* see if at EOM */
                uptr->SNS |= SNS_EOT;       /* set EOT status */
            }

            uptr->CMD &= ~(MT_CMDMSK|MT_READDONE);  /* clear all but readdone & cmd */
            uptr->CMD &= ~MT_CMDMSK;        /* clear the cmd */
            mt_busy[bufnum] &= ~1;          /* not busy anymore */
            sim_debug(DEBUG_CMD, dptr,
                "mt_srv READ %04x char complete unit=%02x sense %08x\n",
                uptr->POS, unit, uptr->SNS);
            if (uptr->SNS & SNS_EOT)
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);  /* set CE, DE, UE */
            else
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* set CE, DE */
            break;
        }
        /* read is not completed, get an input char */
        /* If empty buffer, fill */
        if (BUF_EMPTY(uptr)) {
            m = chp->ccw_addr & MASK24;     /* memory buffer address */
            /* buffer is empty, so fill it with next record data */
            if ((r = sim_tape_rdrecf(uptr, &mt_buffer[bufnum][0], &reclen, BUFFSIZE)) != MTSE_OK) {
                sim_debug(DEBUG_CMD, dptr, "mt_srv READ fill buffer unit=%02x\n", unit);
                uptr->CMD &= ~(MT_CMDMSK|MT_READDONE);  /* clear all but readdone & cmd */
                return mt_error(uptr, chsa, r, dptr);   /* process any error & return status */
            }
            uptr->SNS &= ~(SNS_LOAD|SNS_EOT);   /* reset BOT & EOT */
            uptr->POS = 0;                  /* reset buffer position */
            uptr->hwmark = reclen;          /* set buffer chars read in */
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ fill buffer %06x complete count %04x\n", m, reclen);

            bufp = dump_buf(&mt_buffer[bufnum][0], 0, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ buf %s\n", bufp);
            bufp = dump_buf(&mt_buffer[bufnum][0], 16, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ buf %s\n", bufp);
            bufp = dump_buf(&mt_buffer[bufnum][0], 32, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ buf %s\n", bufp);

            m = chp->ccw_addr & MASK24;     /* memory buffer address */
            bufp = dump_mem(m, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ mem %s\n", bufp);
            bufp = dump_mem(m+16, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ mem %s\n", bufp);
            bufp = dump_mem(m+32, 16);
            sim_debug(DEBUG_CMD, dptr, "mt_srv READ mem %s\n", bufp);
        }
        /* get a char from the buffer */
        ch = mt_buffer[bufnum][uptr->POS++];

        /* Send character over to channel */
        if (chan_write_byte(chsa, &ch)) {
            sim_debug(DEBUG_CMD, dptr,
                "Read unit %02x EOR cnt %04x hwm %04x\n", unit, uptr->POS-1, uptr->hwmark);
            /* If not read whole record, skip till end */
            if ((uint32)uptr->POS < uptr->hwmark) {
                /* Send dummy character to force SLI */
                chan_write_byte(chsa, &ch); /* write the byte */
                sim_debug(DEBUG_CMD, dptr, "Read unit %02x send dump SLI\n", unit);
                sim_activate(uptr, (uptr->hwmark-uptr->POS) * 4); /* wait again */
                uptr->CMD |= MT_READDONE;   /* read is done */
                break;
            }
            sim_debug(DEBUG_CMD, dptr,
                "Read data @1 unit %02x cnt %04x ch %02x hwm %04x\n",
                    unit, uptr->POS, ch, uptr->hwmark);
#ifndef OLDWAY
            uptr->CMD &= ~MT_CMDMSK;        /* clear the cmd */
            mt_busy[bufnum] &= ~1;          /* set not busy */
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* return end status */
#else
            sim_activate(uptr, 50);
            uptr->CMD |= MT_READDONE;       /* read is done */
            break;
#endif
        } else {
            sim_debug(DEBUG_DATA, dptr,
                "Read data @2 unit %02x cnt %04x ch %02x hwm %04x\n",
                unit, uptr->POS, ch, uptr->hwmark);
            if ((uint32)uptr->POS >= uptr->hwmark) { /* In IRG */
                /* Handle end of data record */
                sim_debug(DEBUG_CMD, dptr,
                    "Read end of data unit %02x cnt %04x ch %02x hwm %04x\n",
                    unit, uptr->POS, ch, uptr->hwmark);
                uptr->CMD |= MT_READDONE;   /* read is done */
                goto reread;
            } else
                goto reread;
        }
        break;

    case MT_WRITE:  /* 0x01 */              /* write record */
        /* Check if write protected */
        if (sim_tape_wrp(uptr)) {
            uptr->SNS |= SNS_CMDREJ;
            uptr->CMD &= ~MT_CMDMSK;
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Write write protected unit=%02x\n", unit);
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
            break;
        }

rewrite:
        /* Grab data until channel has no more */
        if (chan_read_byte(chsa, &ch)) {
            if (uptr->POS > 0) {            /* Only if data in record */
                reclen = uptr->hwmark;
                sim_debug(DEBUG_CMD, dptr, "Write unit=%02x Block %04x chars\n",
                    unit, reclen);
                r = sim_tape_wrrecf(uptr, &mt_buffer[bufnum][0], reclen);
                uptr->POS = 0;
                uptr->CMD &= ~MT_CMDMSK;
                mt_error(uptr, chsa, r, dptr);  /* Record errors */
            }
        } else {
            mt_buffer[bufnum][uptr->POS++] = ch;
            sim_debug(DEBUG_DATA, dptr, "Write data unit=%02x %04x %02x\n",
                unit, uptr->POS, ch);
            uptr->hwmark = uptr->POS;
            goto rewrite;
        }
        break;

    case MT_RDBK:   /* 0x0C */              /* Read Backwards */
        if (uptr->CMD & MT_READDONE) {
            uptr->CMD &= ~(MT_CMDMSK|MT_READDONE);
            mt_busy[bufnum] &= ~1;
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);
            return SCPE_OK;
        }

        /* If at end of record, fill buffer */
        if (BUF_EMPTY(uptr)) {
            if (sim_tape_bot(uptr)) {
                uptr->CMD &= ~MT_CMDMSK;
                mt_busy[GET_DEV_BUF(dptr->flags)] &= ~1;
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
                return SCPE_OK;
            }
            sim_debug(DEBUG_CMD, dptr, "Read backward unit=%02x\n", unit);
            if ((r = sim_tape_rdrecr(uptr, &mt_buffer[bufnum][0], &reclen, BUFFSIZE)) != MTSE_OK) {
                uptr->CMD &= ~(MT_CMDMSK|MT_READDONE);
                return mt_error(uptr, chsa, r, dptr);
            }
            uptr->POS = reclen;
            uptr->hwmark = reclen;
            sim_debug(DEBUG_CMD, dptr, "Binary Block %04x chars\n", reclen);
        }

        ch = mt_buffer[bufnum][--uptr->POS];

        if (chan_write_byte(chsa, &ch)) {
            sim_debug(DEBUG_CMD, dptr, "Read unit=%02x EOR cnt %04x\n",
                unit, uptr->POS);
            /* If not read whole record, skip till end */
            if (uptr->POS >= 0) {
                sim_activate(uptr, (uptr->POS) * 10);
                uptr->CMD |= MT_READDONE;
                return SCPE_OK;
            }
            uptr->CMD &= ~MT_CMDMSK;
            mt_busy[bufnum] &= ~1;
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);
        } else {
            sim_debug(DEBUG_CMD, dptr, "Read data unit=%02x %04x %02x\n",
                unit, uptr->POS, ch);
            if (uptr->POS == 0) {           /* In IRG */
                uptr->CMD &= ~MT_CMDMSK;
                mt_busy[bufnum] &= ~1;
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND);
            } else
                sim_activate(uptr, 30);
        }
        break;

    case MT_WTM:    /* 0x93 */              /* Write tape filemark */
        if (uptr->POS == 0) {
            if (sim_tape_wrp(uptr)) {
                uptr->SNS |= SNS_CMDREJ;
                uptr->CMD &= ~MT_CMDMSK;
                mt_busy[GET_DEV_BUF(dptr->flags)] &= ~1;
                chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
                return SCPE_OK;
            }
            uptr->POS ++;
            sim_activate(uptr, 100);
        } else {
            sim_debug(DEBUG_CMD, dptr, "Write Mark unit=%02x\n", unit);
            uptr->CMD &= ~(MT_CMDMSK);
            r = sim_tape_wrtmk(uptr);
            chan_end(chsa, SNS_DEVEND);
            mt_busy[bufnum] &= ~1;
        }
        break;

    case MT_BSR:    /* 0x53 */              /* Backspace record */
        sim_debug(DEBUG_CMD, dptr, "mt_srv cmd 0x53 BSR unit %02x POS %x SNS %08x\n",
            unit, uptr->POS, uptr->SNS);
        switch (uptr->POS) {
        case 0:
            if (sim_tape_bot(uptr)) {
                uptr->CMD &= ~MT_CMDMSK;
                 mt_busy[GET_DEV_BUF(dptr->flags)] &= ~1;
                 chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
                 return SCPE_OK;
            }
            uptr->POS++;
            sim_activate(uptr, 30);
            break;
        case 1:
            uptr->POS++;
            r = sim_tape_sprecr(uptr, &reclen);
            sim_debug(DEBUG_CMD, dptr, "Backspace rec unit %02x POS %x r %x\n",
                unit, uptr->POS, r);
            /* SEL requires Unit Except & EOF on EOF */
            if (r == MTSE_TMK) {            /* test for EOF */
                uptr->POS++;
                sim_debug(DEBUG_CMD, dptr, "BSR got EOF MARK\n");
                sim_activate(uptr, 30);
            /* SEL requires Unit Except & BOT on BOT */
            } else if (r == MTSE_BOT) {
                uptr->POS+= 2;
                sim_debug(DEBUG_CMD, dptr, "BSR got BOT\n");
                sim_activate(uptr, 30);
            } else {
                sim_debug(DEBUG_CMD, dptr, "Backspace reclen %04x SNS %08x\n", reclen, uptr->SNS);
                sim_activate(uptr, 30);
            }
            break;
        case 2:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Backspace record completed with NO status\n");
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);
            break;
        case 3:     /* EOF */
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            uptr->SNS |= SNS_FMRKDT;        /* file mark detected */
            sim_debug(DEBUG_CMD, dptr, "Backspace record completed with EOF status\n");
            chan_end(chsa, SNS_DEVEND|SNS_UNITEXP);
            break;
        case 4:     /* BOT */
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            uptr->SNS |= SNS_LOAD;          /* set BOT detected */
            sim_debug(DEBUG_CMD, dptr, "Backspace record completed with BOT status\n");
            chan_end(chsa, SNS_DEVEND|SNS_UNITEXP);
            break;
        }
        break;

    case MT_BSF:    /* 0x73 */              /* Backspace file */
        sim_debug(DEBUG_CMD, dptr, "mt_srv cmd 0x73 BSF unit %02x POS %04x\n",
            unit, uptr->POS);
        switch(uptr->POS) {
        case 0:
            if (sim_tape_bot(uptr)) {
                uptr->CMD &= ~MT_CMDMSK;
                 mt_busy[bufnum] &= ~1;
                 chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITCHK);
                 break;
            }
            uptr->POS++;
            sim_activate(uptr, 100);
            break;
        case 1:
//#define NBSF
#ifdef NBSF
            skip = 1;               /* skip 1 file */
#endif
            uptr->SNS &= ~(SNS_LOAD|SNS_EOT|SNS_FMRKDT);    /* reset BOT, EOT, EOF */
#ifdef NBSF
            /* using the backspace file call on simh does not work with MPX */
            r = sim_tape_spfiler(uptr, skip, &reclen);
            uptr->POS++;
#else
            r = sim_tape_sprecr(uptr, &reclen);
#endif
            sim_debug(DEBUG_CMD, dptr, "Backspace file unit=%02x r %x\n", unit, r);
            if (r == MTSE_TMK) {
                uptr->POS++;
                sim_debug(DEBUG_CMD, dptr, "BSF got EOF MARK\n");
                sim_activate(uptr, 30);
            } else if (r == MTSE_BOT) {
                uptr->POS+= 2;
                sim_debug(DEBUG_CMD, dptr, "BSF got BOT\n");
                sim_activate(uptr, 30);
            } else {
                /* already there */
                sim_debug(DEBUG_CMD, dptr, "Backspace file reclen %04x\n", reclen);
                sim_activate(uptr, 20);
            }
            break;
#ifdef NBSF
        case 2:
            uptr->CMD &= ~(MT_CMDMSK);
            /* no EOF detected, but we did go back 1 record */
            uptr->SNS |= SNS_FMRKDT;        /* file mark detected */
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Backspace file Completed with NO EOF status\n");
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
            break;
#endif
#ifdef NBSF
        case 3:        /* File Mark */
#else
        case 2:        /* File Mark */
#endif
            uptr->CMD &= ~(MT_CMDMSK);
            uptr->SNS |= SNS_FMRKDT;        /* file mark detected */
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Backspace file Completed with EOF status\n");
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
            break;
#ifdef NBSF
        case 4:        /* BOT */
#else
        case 3:        /* BOT */
#endif
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            uptr->SNS |= SNS_LOAD;          /* set BOT detected */
            sim_debug(DEBUG_CMD, dptr, "Backspace file Completed with BOT status\n");
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
            break;
        }
        break;

    case MT_FSR:    /* 0x43 */              /* Advance record */
        switch(uptr->POS) {
        case 0:
            sim_debug(DEBUG_CMD, dptr, "Skip rec entry unit=%02x POS %x\n", unit, uptr->POS);
            uptr->POS++;
            sim_activate(uptr, 30);
            break;
        case 1:
            uptr->POS++;
            uptr->SNS &= ~(SNS_LOAD|SNS_EOT|SNS_FMRKDT);    /* reset BOT, EOT, EOF */
            r = sim_tape_sprecf(uptr, &reclen);
            sim_debug(DEBUG_CMD, dptr, "Skip rec unit=%02x r %x\n", unit, r);
            if (r == MTSE_TMK) {
                uptr->POS = 3;
                uptr->SNS |= SNS_FMRKDT;    /* file mark detected */
                sim_debug(DEBUG_CMD, dptr, "FSR got EOF MARK\n");
                sim_activate(uptr, 30);
            } else if (r == MTSE_EOM) {
                uptr->POS = 4;
                uptr->SNS |= SNS_EOT;       /* set EOT status */
                sim_debug(DEBUG_CMD, dptr, "FSR got EOT\n");
                sim_activate(uptr, 30);
            } else {
                sim_debug(DEBUG_CMD, dptr, "FSR skipped %04x byte record\n",
                    reclen);
                sim_activate(uptr, 30);
            }
            break;
        case 2:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Skip record Completed\n");
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
            break;
        case 3:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Skip record now at EOF\n");
            chan_end(chsa, SNS_DEVEND|SNS_UNITEXP);
            break;
        case 4:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr, "Skip record now at EOT\n");
            chan_end(chsa, SNS_DEVEND|SNS_UNITEXP);
            break;
        }
        break;

    case MT_FSF:    /* 0x63 */              /* advance filemark */
        switch(uptr->POS) {
        case 0:
            sim_debug(DEBUG_CMD, dptr,
                "Skip file entry sense %08x unit %02x\n", uptr->SNS, unit);
            uptr->POS++;
            sim_activate(uptr, 30);
            break;
        case 1:
            skip = 1;                       /* skip forward 1 file */
            uptr->POS++;
            uptr->SNS &= ~(SNS_LOAD|SNS_EOT|SNS_FMRKDT);    /* reset BOT, EOT, EOF */
            r = sim_tape_spfilef(uptr, skip, &reclen);
            sim_debug(DEBUG_CMD, dptr, "Skip file unit=%02x r %x\n", unit, r);
            if (r == MTSE_TMK) {
                uptr->POS++;
                uptr->SNS |= SNS_FMRKDT;    /* file mark detected */
                sim_debug(DEBUG_CMD, dptr, "FSF EOF MARK sense %08x\n", uptr->SNS);
                sim_activate(uptr, 30);
            } else if (r == MTSE_EOM) {
                uptr->SNS |= SNS_EOT;       /* set EOT status */
                sim_debug(DEBUG_CMD, dptr, "FSF EOT sense %08x\n", uptr->SNS);
                uptr->POS+= 2;
                sim_activate(uptr, 30);
            } else {
                sim_debug(DEBUG_CMD, dptr, "FSF skipped %04x file\n", reclen);
                sim_activate(uptr, 30);
            }
            break;
        case 2:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr,
                "Skip file done sense %08x unit %02x\n", uptr->SNS, unit);
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
            break;
        case 3:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr,
                "Skip file got EOF sense %08x unit %02x\n", uptr->SNS, unit);
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
            break;
        case 4:
            uptr->CMD &= ~(MT_CMDMSK);
            uptr->SNS |= SNS_EOT;           /* set EOT status */
            mt_busy[bufnum] &= ~1;
            sim_debug(DEBUG_CMD, dptr,
                "Skip file got EOT sense %08x unit %02x\n", uptr->SNS, unit);
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND|SNS_UNITEXP);
            break;
        }
        break;

    case MT_ERG:    /* 0xA3 */              /* Erace 3.5 in tape */
        switch (uptr->POS) {
        case 0:
            if (sim_tape_wrp(uptr)) {
                uptr->SNS |= SNS_CMDREJ;
                uptr->CMD &= ~MT_CMDMSK;
                mt_busy[bufnum] &= ~1;
                chan_end(chsa, SNS_DEVEND|SNS_UNITEXP);
            } else {
                uptr->POS ++;
                sim_activate(uptr, 50);
            }
            break;
        case 1:
            sim_debug(DEBUG_CMD, dptr, "Erase unit=%02x\n", unit);
            r = sim_tape_wrgap(uptr, 35);
            sim_activate(uptr, 100);
            uptr->POS++;
            break;
        case 2:
            uptr->CMD &= ~(MT_CMDMSK);
            mt_busy[bufnum] &= ~1;
            /* we are done dev|chan end */
            chan_end(chsa, SNS_DEVEND);
            break;
        }
        break;

    case MT_REW:    /* 0x23 */              /* rewind tape */
        if (uptr->POS == 0) {
            uptr->POS++;
            sim_debug(DEBUG_CMD, dptr, "Start rewind unit %02x\n", unit);
            sim_activate(uptr, 2500);
        } else {
            sim_debug(DEBUG_CMD, dptr, "Rewind complete unit %02x\n", unit);
            uptr->CMD &= ~(MT_CMDMSK);
            r = sim_tape_rewind(uptr);
            uptr->SNS |= SNS_LOAD;          /* set BOT */
            mt_busy[bufnum] &= ~1;
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
         }
         break;

    case MT_RUN:    /* 0x33 */              /* Rewind and unload tape */
         if (uptr->POS == 0) {
             uptr->POS++;
             mt_busy[bufnum] &= ~1;
             sim_debug(DEBUG_CMD, dptr, "Start rewind/unload unit %02x\n", unit);
             sim_activate(uptr, 300);
         } else {
            sim_debug(DEBUG_CMD, dptr, "Unload unit=%02x\n", unit);
            uptr->CMD &= ~(MT_CMDMSK);
            uptr->SNS |= SNS_INTVENT;       /* unit intervention required */
            uptr->SNS &= ~(SNS_RDY|SNS_ONLN);   /* unit not online or rdy */
            uptr->SNS &= ~SNS_LOAD;         /* reset BOT detected */
             r = sim_tape_detach(uptr);
            chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* we are done dev|chan end */
         }
         break;
    }
    return SCPE_OK;
}

/* initialize the tape chan/unit */
void mt_ini(UNIT *uptr, t_bool f)
{
    DEVICE *dptr = get_dev(uptr);
    if (MT_DENS(uptr->dynflags) == 0)
        uptr->dynflags |= MT_DENS_6250 << UNIT_S_DF_TAPE;

    uptr->CMD &= ~0xffff;                   /* clear out the flags but leave ch/sa */
    uptr->SNS = 0;                          /* clear sense data */
    uptr->SNS |= (SNS_RDY|SNS_ONLN);        /* set initial status */
    mt_busy[GET_DEV_BUF(dptr->flags)] = 0;  /* set not busy */
    sim_cancel(uptr);                       /* cancel any timers */
    sim_debug(DEBUG_EXP, dptr, "MT init device %s unit %02x\n",
        dptr->name, GET_UADDR(uptr->CMD));
}

/* handle rschnlio cmds for tape */
t_stat  mt_rschnlio(UNIT *uptr) {
    DEVICE *dptr = get_dev(uptr);
    uint16  chsa = GET_UADDR(uptr->CMD);
    int     cmd = uptr->CMD & MT_CMDMSK;

    sim_debug(DEBUG_EXP, dptr, "mt_rschnl chsa %04x cmd = %02x\n", chsa, cmd);
    mt_ini(uptr, 0);                        /* reset the unit */
    return SCPE_OK;
}

/* Handle haltio transfers for mag tape */
t_stat  mt_haltio(UNIT *uptr) {
    uint16  chsa = GET_UADDR(uptr->CMD);
    int     cmd = uptr->CMD & MT_CMDMSK;
    CHANP   *chp = find_chanp_ptr(chsa);    /* find the chanp pointer */
    DEVICE  *dptr = get_dev(uptr);

    sim_debug(DEBUG_EXP, dptr,
        "mt_haltio enter chsa %04x cmd = %02x\n", chsa, cmd);

    /* terminate any input command */
    /* UTX wants SLI bit, but no unit exception */
    /* status must not have an error bit set */
    /* otherwise, UTX will panic with "bad status" */
    if (cmd != 0) {                         /* is unit busy */
        sim_debug(DEBUG_CMD, dptr,
            "mt_haltio HIO chsa %04x cmd = %02x ccw_count %02x\n",
            chsa, cmd, chp->ccw_count);
        sim_cancel(uptr);                   /* stop timer */
    } else {
        sim_debug(DEBUG_CMD, dptr,
            "mt_haltio HIO not busy chsa %04x cmd = %02x ccw_count %02x\n",
            chsa, cmd, chp->ccw_count);
    }
    /* stop any I/O and post status and return error status */
    uptr->CMD &= LMASK;                     /* make non-busy */
    uptr->POS = 0;                          /* clear position data */
    uptr->SNS = SNS_RDY|SNS_ONLN;           /* status is online & ready */
    chp->ccw_count = 0;                     /* zero the count */
    chp->ccw_flags &= ~(FLAG_DC|FLAG_CC);   /* reset chaining bits */
    sim_debug(DEBUG_CMD, dptr,
        "mt_haltio HIO I/O stop chsa %04x cmd = %02x\n", chsa, cmd);
    chan_end(chsa, SNS_CHNEND|SNS_DEVEND);  /* force end */
    return SCPE_IOERR;                      /* tell chan code to post status */
}
/* reset the mag tape */
t_stat mt_reset(DEVICE *dptr)
{
    /* nothing to do?? */
    sim_debug(DEBUG_EXP, dptr, "MT reset name %s\n", dptr->name);
    return SCPE_OK;
}

/* attach the specified file to the tape device */
t_stat mt_attach(UNIT *uptr, CONST char *file)
{
    uint16      chsa = GET_UADDR(uptr->CMD);    /* get address of mt device */
    CHANP       *chp = find_chanp_ptr(chsa);    /* get channel prog pointer */
    DEVICE      *dptr = get_dev(uptr);      /* get device pointer */
    t_stat      r;
    DIB         *dibp = 0;
    struct attfileinfo *afi;

    if (dptr->flags & DEV_DIS) {
        fprintf(sim_deb, "ERROR===ERROR\nMT device %s disabled on system, aborting\r\n",
            dptr->name);
        printf("ERROR===ERROR\nMT device %s disabled on system, aborting\r\n",
            dptr->name);
        return SCPE_UDIS;                   /* device disabled */
    }

    /* mount the specified file to the MT */
    if ((r = sim_tape_attach(uptr, file)) != SCPE_OK) {
       fprintf(sim_deb, "mt_attach ERROR filename %s status %08x\r\n", file, r);
       return r;                            /* report any error */
    }
    sim_debug(DEBUG_EXP, dptr, "mt_attach complete filename %s\n", file);
    uptr->CMD &= ~0xffff;                   /* clear out the flags but leave ch/sa */
    uptr->POS = 0;                          /* clear position data */
    uptr->SNS = 0;                          /* clear sense data */
    uptr->SNS |= SNS_ONLN;                  /* 0x40 Drive Online */

    /* check for valid configured tape */
    /* must have valid DIB and Channel Program pointer */
    dibp = (DIB *)dptr->ctxt;               /* get the DIB pointer */
    if ((dib_unit[chsa] == NULL) || (dibp == NULL) || (chp == NULL)) {
        sim_debug(DEBUG_CMD, dptr,
            "ERROR===ERROR\nMT device %s not configured on system, aborting\n",
            dptr->name);
        printf("ERROR===ERROR\nMT device %s not configured on system, aborting\r\n",
            dptr->name);
        fprintf(sim_deb, "ERROR===ERROR\nMT device %s not configured on system, aborting\r\n",
            dptr->name);
        detach_unit(uptr);                  /* detach if error */
        return SCPE_UNATT;                  /* error */
    }
    set_devattn(chsa, SNS_DEVEND);          /* ready int???? */

    /* store filename of attached device */
    afi = att_files;
    while (afi != (struct attfileinfo *) NULL && afi->chsa != chsa)
        afi = afi->next;
    /* only when valid entry found */
    if (afi != (struct attfileinfo *) NULL) {
        /* clear filename if previously stored filename differs */
        if (strcmp(file, afi->att_file) != 0)
            afi->att_file[0] = '\0';
    } else {
        /* create new entry */
        afi = att_files;
        att_files = malloc(sizeof(struct attfileinfo));
        if (att_files != (struct attfileinfo *) NULL)
            att_files->next = afi;
        afi = att_files;

        /* forget it if malloc failed */
        if (afi != (struct attfileinfo *) NULL) {
            afi->chsa = chsa;
            afi->att_file = strdup(file);
        }
     }

    return SCPE_OK;                         /* return good status */
}

/* detach the MT device and unload any tape */
t_stat mt_detach(UNIT *uptr)
{
    DEVICE      *dptr = get_dev(uptr);      /* get device pointer */
    sim_debug(DEBUG_EXP, dptr, "mt_detach\n");
    uptr->CMD &= ~0xffff;                   /* clear out the flags but leave ch/sa */
    uptr->POS = 0;                          /* clear position data */
    uptr->SNS = 0;                          /* clear sense data */
    uptr->flags &= ~MTUF_WRP;               /* clear write protect */
    uptr->flags &= ~UNIT_RO;                /* clear read only */
    return sim_tape_detach(uptr);
}

/* boot from the specified tape unit */
t_stat mt_boot(int32 unit_num, DEVICE *dptr)
{
    UNIT    *uptr = &dptr->units[unit_num]; /* find tape unit pointer */

    /* see if device disabled */
    if (dptr->flags & DEV_DIS) {
        printf("ERROR===ERROR\r\nMT device %s disabled on system, aborting\r\n",
            dptr->name);
        return SCPE_UDIS;                   /* device disabled */
    }
    sim_debug(DEBUG_EXP, dptr, "MT Boot dev/unit %04x\n", GET_UADDR(uptr->CMD));
    printf("MT Boot dev/unit %04x\r\n", GET_UADDR(uptr->CMD));
    if ((uptr->flags & UNIT_ATT) == 0) {    /* Is MT device already attached? */
        sim_debug(DEBUG_EXP, dptr,
            "MT Boot attach error dev/unit %04x\n", GET_UADDR(uptr->CMD));
        printf("MT Boot attach error dev/unit %04x\r\n", GET_UADDR(uptr->CMD));
        return SCPE_UNATT;                  /* not attached, return error */
    }
    SPAD[0xf4] = GET_UADDR(uptr->CMD);      /* put boot device chan/sa into spad */
    SPAD[0xf8] = 0xF000;                    /* show as F class device */

    uptr->CMD &= ~0xffff;                   /* clear out old status */
    return chan_boot(GET_UADDR(uptr->CMD), dptr);   /* boot the ch/sa */
}

t_stat mt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
   char buffer[256];
   fprintf (st, "%s\n\n", mt_description(dptr));
   fprintf (st, "The mag tape drives support the BOOT command\n\n");
   (void)sim_tape_density_supported (buffer, sizeof(buffer), valid_dens);
   fprintf (st, " The density of the mag tape drive can be set with\n");
   fprintf (st, "    SET %s DENSITY=%s\n\n", dptr->name, buffer);
   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 "8051 Buffered Tape Processor";
}

#endif /* NUM_DEVS_MT */
