/* i7090_disk.c: IBM 7090 Disk

   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 CORNRWELL 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.

   Support for 1301/1302/2302 disks and 7238 drums

   Disks are represented in files as follows:

   Since these drives supported variable format for each cylinder the
   format is represented as one track per cylinder as follows:

     0          data
     1          header
     2          Home Address
     3          end of track

   These codes are packed 4 per byte and used to control read/write of
   data.

   After one format track per cylinder there is one record of bytes per
   track data for each track. First bytes are home address 2, followed
   by record address, and record data to cover number in format. All data
   is stored with the top 2 bits as zero.

   Limitiation of this are that the address field for each record can not
   be more then 16 bytes.

*/

#include "i7000_defs.h"

#ifdef NUM_DEVS_DSK
#define UNIT_DSK        UNIT_ATTABLE | UNIT_DISABLE | UNIT_FIX
#define FORMAT_OK       (1 << (UNIT_V_LOCAL+0))
#define HA2_OK          (1 << (UNIT_V_LOCAL+1))
#define CTSS_BOOT       (1 << (UNIT_V_MODE))

/* Device status information stored in u5 */
#define DSKSTA_CMD      0x0000100       /* Unit has recieved a cmd */
#define DSKSTA_DATA     0x0000200       /* Unit has finished cmd */
#define DSKSTA_WRITE    0x0000400       /* Last command was a write */
#define DSKSTA_CHECK    0x0000800       /* Doing a write check */
#define DSKSTA_CMSK     0x00000ff       /* Command mask */
#define DSKSTA_ARGMSK   0x0fff000       /* Command argument */
#define DSKSTA_ARGSHFT  12
#define DSKSTA_SCAN     0x1000000       /* Scanning for header */
#define DSKSTA_SKIP     0x2000000       /* Skiping record */
#define DSKSTA_XFER     0x4000000       /* Tranfser current record */
#define DSKSTA_DIRTY    0x8000000       /* Buffer needs to be written */

#define FMT_DATA        0               /* Data */
#define FMT_HDR         1               /* Header */
#define FMT_HA2         2               /* Home address 2 */
#define FMT_END         3               /* End of track */

/* Disk commands */
#define DNOP            0x00            /* Nop */
#define DREL            0x04            /* Release */
#define DEBM            0x08            /* Eight Bit mode */
#define DSBM            0x09            /* Six bit mode */
#define DSEK            0x80            /* Seek */
#define DVSR            0x82            /* Prepare to Verify single record */
#define DWRF            0x83            /* Prepare to Format */
#define DVTN            0x84            /* Prepare to Verify track no addr */
#define DVCY            0x85            /* Prepare to Verify Cyl */
#define DWRC            0x86            /* Prepare to Write Check */
#define DSAI            0x87            /* Set Access Inoperative */
#define DVTA            0x88            /* Prepare to Verify track addr */
#define DVHA            0x89            /* Prepare to Verify home addr */

/* Disk sense codes */  /*01234 */
#define STAT_SIXBIT     0x00004         /* Disk in 6bit more. */
#define EXPT_FILECHK    0x10010         /* File control check error */
#define EXPT_DSKCHK     0x10020         /* Disk storage error */
#define STAT_NOTRDY     0x10040         /* Disk no ready */
#define STAT_OFFLINE    0x10080         /* Disk offline */
#define DATA_PARITY     0x20100         /* Data parity error */
#define DATA_CHECK      0x20200         /* Compare error */
#define DATA_RESPONSE   0x20400         /* Response check */
#define PROG_INVADDR    0x40800         /* Invalid seek address */
#define PROG_NOREC      0x41000         /* No record found */
#define PROG_FMTCHK     0x42000         /* Format check */
#define PROG_INVCODE    0x44000         /* Invalid code */
#define PROG_INVSEQ     0x48000         /* Invalid sequence */

#define MAXTRACK        6020    /* Max size per track */

uint32              dsk_cmd(UNIT *, uint16, uint16);
t_stat              dsk_srv(UNIT *);
t_stat              dsk_boot(int32, DEVICE *);
void                dsk_ini(UNIT *, t_bool);
t_stat              dsk_reset(DEVICE *);
t_stat              dsk_set_module(UNIT * uptr, int32 val, CONST char *cptr,
                                   void *desc);
t_stat              dsk_get_module(FILE * st, UNIT * uptr, int32 v,
                                   CONST void *desc);
t_stat              dsk_set_type(UNIT * uptr, int32 val, CONST char *cptr,
                                 void *desc);
t_stat              dsk_get_type(FILE * st, UNIT * uptr, int32 v,
                                 CONST void *desc);
t_stat              dsk_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag,
                        const char *cptr);
const char          *dsk_description (DEVICE *dptr);

int                 disk_rblock(UNIT * uptr, int track);
int                 disk_wblock(UNIT * uptr);
void                disk_posterr(UNIT * uptr, uint32 error);
void                disk_cmderr(UNIT * uptr, uint32 error);
int                 disk_cmd(UNIT * uptr);
int                 disk_write(UNIT * uptr, uint8 data, int chan,
                               int eor);
int                 disk_read(UNIT * uptr, uint8 * data, int chan);
int                 disk_format(UNIT * uptr, FILE * f, int cyl,
                                UNIT * base);
int                 bcd_to_track(uint32 addr);

/* Data buffer for track */
uint8               dbuffer[NUM_DEVS_DSK * 4][MAXTRACK];

/* Format buffer for cylinder */
uint8               fbuffer[NUM_DEVS_DSK * 4][MAXTRACK / 4];

/* Currently loaded format record */
uint16              fmt_cyl[NUM_DEVS_DSK * 4];

/* Currently read in track in buffer */
uint16              dtrack[NUM_DEVS_DSK * 4];

/* Arm position */
uint16              arm_cyl[NUM_DEVS_DSK * 4];
uint32              sense[NUM_CHAN * 2];
uint32              sense_unit[NUM_CHAN * 2];
uint8               cmd_buffer[NUM_CHAN];       /* Command buffer per channel */
uint8               cmd_mod[NUM_CHAN];          /* Command module per channel */
uint32              cmd_option[NUM_CHAN];       /* Command option per channel */
uint16              cmd_count[NUM_CHAN];        /* Number of chars recieved */

#ifdef I7010
extern uint8        chan_seek_done[NUM_CHAN];   /* Seek finished flag */
extern uint8        chan_io_status[NUM_CHAN];   /* Channel status flags */
#endif

/* Macro to help build the disk size table */
#define DISK_DEF(name, cyl, cylpertrk, acc, charpertrk, overhd, mod, dr) \
        { name, cyl, cylpertrk, ((charpertrk/128) + 1) * 128, \
         acc, (((charpertrk/128) + 1) * 128)/4, \
          acc * cyl * ((((charpertrk/128) + 1) * 128)/4), \
        overhd, mod, dr }

struct disk_t
{
    const char         *name;   /* Type Name */
    int                 cyl;    /* Number of cylinders */
    int                 track;  /* Number of tracks/cylinder */
    unsigned int        bpt;    /* Max bytes per track */
    int                 arms;   /* Number of access arms */
    int                 fbpt;   /* Number of format bytes per track */
    int                 fmtsz;  /* Format size */
    int                 overhd; /* Number of characters overhead on HA/RA */
    int                 mods;   /* Number of modules */
    int                 datarate;       /* us per chars */
}
disk_type[] =
{
        DISK_DEF("1301", 254, 40, 1, 2880, 4, 1, 15),
        DISK_DEF("1301-2", 254, 40, 1, 2880, 4, 2, 15),
        DISK_DEF("1302", 254, 40, 2, 5940, 7, 1, 10),
        DISK_DEF("1302-2", 254, 40, 2, 5940, 7, 2, 10),
        DISK_DEF("2302", 254, 40, 2, 5940, 7, 2, 10),
        DISK_DEF("7238", 1, 404, 1, 3270, 4, 1, 10),
        DISK_DEF("7238-2", 1, 404, 1, 3270, 4, 2, 10), {
        NULL, 0}
};

int                 unit_bit[] = {
  /*0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
   19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 30, 30, 30, 30, 30, 30,
    9,  8,  7,  6,  5,  4,  3,  2,  1,  0, 30, 30, 30, 30, 30, 30
};

#define DSKSTA_BSY      0x02    /* Controller busy. */
#define DSKSTA_EIGHT    0x04    /* Controller in 8 bit mode */

#ifdef I7090
#define CH1 4
#define CH2 6
#endif
#ifdef I7070
#define CH1 5
#define CH2 6
#endif
#ifdef I7080
#define CH1 5
#define CH2 6
#endif
#ifndef CH1
#define CH1 1
#endif
#ifndef CH2
#define CH2 2
#endif

UNIT                dsk_unit[] = {
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH1) | UNIT_DSK, 0), 0, 0x000, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH1) | UNIT_DSK, 0), 0, 0x102, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH1) | UNIT_DSK, 0), 0, 0x204, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH1) | UNIT_DSK, 0), 0, 0x306, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH1) | UNIT_DSK, 0), 0, 0x408, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH2) | UNIT_DSK, 0), 0, 0x500, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH2) | UNIT_DSK, 0), 0, 0x602, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH2) | UNIT_DSK, 0), 0, 0x704, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH2) | UNIT_DSK, 0), 0, 0x806, 0},
    {UDATA(&dsk_srv, UNIT_S_CHAN(CH2) | UNIT_DSK, 0), 0, 0x908, 0},
/* Second set for arm two of each unit */
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
/* Third set for arm one of second module */
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
/* Fourth set for arm two of second module */
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
    {UDATA(&dsk_srv, UNIT_DIS, 0), 0, 0xff, 0},
};

MTAB                dsk_mod[] = {
    {FORMAT_OK, 0, 0, "NOFORMAT", NULL, NULL, NULL, "Format not allowed"},
    {FORMAT_OK, FORMAT_OK, "FORMAT", "FORMAT", NULL, NULL, NULL,
             "Format allowed"},
    {HA2_OK, 0, 0, "NOHA2", NULL, NULL, NULL, "No writing of Home Address"},
    {HA2_OK, HA2_OK, "HA2", "HA2", NULL, NULL, NULL,
            "Allow writing of Home Address"},
#ifdef I7090
    {CTSS_BOOT, 0, 0, "IBSYS", NULL, NULL, NULL, "IBSYS Boot Card"},
    {CTSS_BOOT, CTSS_BOOT, "CTSS", "CTSS", NULL, NULL, NULL, "CTSS Boot Card"},
#endif
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "TYPE", "TYPE",
     &dsk_set_type, &dsk_get_type, NULL, "Type of disk"},
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "MODULE", "MODULE",
     &dsk_set_module, &dsk_get_module, NULL, "Module number"},
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "CHAN", "CHAN",
     &set_chan, &get_chan, NULL, "Channel number"},
#ifndef I7010
    {MTAB_XTD | MTAB_VUN | MTAB_VALR, 0, "SELECT", "SELECT",
     &chan9_set_select, &chan9_get_select, NULL, "Unit select"},
#endif
    {0}
};

DEVICE              dsk_dev = {
    "DK", dsk_unit, NULL /* Registers */ , dsk_mod,
    NUM_DEVS_DSK, 8, 15, 1, 8, 8,
    NULL, NULL, &dsk_reset, &dsk_boot, NULL, NULL,
    &dsk_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug,
    NULL, NULL, &dsk_help, NULL, NULL, &dsk_description
};

uint32 dsk_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                chan;
    int                 u = (uptr->u3 >> 8) & 0xf;
    UNIT               *base = &dsk_unit[u];
    int                 sel;

    chan = UNIT_G_CHAN(dsk_unit[u].flags);
    sel = (base->flags & UNIT_SELECT) ? 1 : 0;
#ifdef I7010
    if (cmd & 0x100)
        sense[(chan * 2) + sel] |= STAT_SIXBIT;
    else
        sense[(chan * 2) + sel] &= ~STAT_SIXBIT;
    cmd_buffer[chan] = cmd & 0xff;
    cmd_count[chan] = 2;
    sim_debug(DEBUG_CHAN, &dsk_dev, "unit %d = cmd=%02x\n\r",
                dev, cmd & 0xff);
#else
    cmd_buffer[chan] = 0;
    cmd_count[chan] = 0;
    sim_debug(DEBUG_CHAN, &dsk_dev, "unit=%d cmd\n\r", dev);
#endif
    cmd_option[chan] = 0;
    cmd_mod[chan] = 0;
    chan_clear(chan, DEV_SEL);

    /* If device is not active start it working */
    if (!sim_is_active(uptr))
        sim_activate(uptr, us_to_ticks(50));
    return SCPE_OK;
}

t_stat dsk_srv(UNIT * uptr)
{
    int                 chan;
    int                 sel;
    int                 schan;
    int                 dev = uptr->u3 & 0xff;
    int                 u = (uptr->u3 >> 8) & 0xf;
    struct disk_t      *dsk = &disk_type[uptr->u4];
    UNIT               *base = &dsk_unit[u];
    uint8               ch = 0;
    int                 eor = 0;

    chan = UNIT_G_CHAN(base->flags);
    sel = (base->flags & UNIT_SELECT) ? 1 : 0;
    schan = (chan * 2) + sel;
    /* Make sure channel is talking to us */
    if (sel != chan_test(chan, CTL_SEL))
        goto seeking;
    /* Channel has disconnected, abort current operation. */
    if (chan_test(chan, DEV_DISCO)) {
        int                 i;

        /* Scan all units and stop them if they are on this channel */
        for (i = 0; i < NUM_DEVS_DSK; i++) {
            int                 xchan = UNIT_G_CHAN(dsk_unit[i].flags);
            int                 j;

            if (xchan != chan)
                continue;
            for (j = 3 * NUM_DEVS_DSK + i; j >= 0; j -= NUM_DEVS_DSK) {
                disk_wblock(&dsk_unit[j]);      /* Flush block */
                if (dsk_unit[j].u5 & DSKSTA_CMD) {
                    dsk_unit[j].u5 &=
                        ~(DSKSTA_CMD | DSKSTA_XFER | DSKSTA_SCAN | DSKSTA_DATA);
                    sim_cancel(&dsk_unit[j]);
                }
            }
        }
        chan_clear(chan, (DEV_DISCO | DEV_WEOR | DEV_SEL));
        sim_debug(DEBUG_CHAN, &dsk_dev, "unit=%d disconnecting\n\r", dev);
        if (uptr->wait > 0)
            sim_activate(uptr, us_to_ticks(100));
        return SCPE_OK;
    }

    /* Call channel proccess to make sure all date is available. */
    chan_proc();
    /* Handle sending sense data */
    if (chan_test(chan, CTL_SNS)) {
        chan9_clear_error(chan, sel);
        switch(cmd_count[chan]) {
        case 0:
                sim_debug(DEBUG_SNS, &dsk_dev, "unit=%d chan sense=%05x\n", dev,
                         sense[schan]);
                /* fall through */
        case 1: case 2: case 3: case 4:
                ch = (sense[schan] >> (4 * (4 - cmd_count[chan]))) & 0xF;
                break;
        case 9:
                sim_debug(DEBUG_SNS, &dsk_dev, "unit=%d unit sense=%08x\n", dev,
                         sense_unit[schan]);
                eor = DEV_REOR;
                /* fall through */
        case 5: case 6: case 7: case 8:
                ch = (sense_unit[schan] >> (4 * (9 - cmd_count[chan]))) & 0xF;
                break;
        }
        if (ch & 010)
            ch ^= 030;
        sim_debug(DEBUG_SNS, &dsk_dev, "unit=%d sense %d %02o\n\r", dev,
                  cmd_count[chan], ch);
        cmd_count[chan]++;
        switch(chan_write_char(chan, &ch, eor)) {
        case DATA_OK:
                if (eor == 0)
                   break;
        case TIME_ERROR:
        case END_RECORD:
                sense[chan] &= STAT_SIXBIT;
                sense_unit[chan] = 0;
                chan_set(chan, CTL_END);
                chan_clear(chan, DEV_SEL);
                sim_debug(DEBUG_CHAN, &dsk_dev, "unit=%d sense eor\n\r", dev);
                break;
        }
        sim_activate(uptr, us_to_ticks(20));
        return SCPE_OK;
    }

    if (chan_test(chan, CTL_CNTL) && disk_cmd(uptr)) {
        sim_activate(uptr, us_to_ticks(50));
        return SCPE_OK;
    }

    /* Handle writing of data */
    if (chan_test(chan, CTL_WRITE) && uptr->u5 & DSKSTA_CMD) {
        /* Channel asking for end of record? */
        if (chan_stat(chan, DEV_WEOR)) {
            sim_debug(DEBUG_CHAN, &dsk_dev, "Disk chan %d -> weor\n\r", chan);
            while (disk_write(uptr, 0x40, chan, 1) == 0) ;
            disk_wblock(uptr);  /* Flush block */
            uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
            uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
            chan_set(chan, CTL_END|DEV_REOR);
            sim_activate(uptr, us_to_ticks(100));
            return SCPE_OK;
        }
        uptr->u5 |= DSKSTA_WRITE;       /* Flag as write */
        switch(chan_read_char(chan, &ch, 0)) {
        case TIME_ERROR:
            disk_posterr(uptr, DATA_RESPONSE);
            sim_activate(uptr, us_to_ticks(100));
            return SCPE_OK;
        case END_RECORD:
            /* If last word, fill with zeros until we get eor */
            sim_debug(DEBUG_CHAN, &dsk_dev, "Disk chan %d eor\n\r",chan);
            uptr->u5 |= DSKSTA_DATA;
            while (disk_write(uptr, 0x40, chan, 1) == 0) ;
            disk_wblock(uptr);  /* Flush block */
            uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
            chan_set(chan, CTL_END);
            sim_activate(uptr, us_to_ticks(100));
            break;
        case DATA_OK:
            eor = disk_write(uptr, ch, chan, 0);
            uptr->u5 |= DSKSTA_DATA;
            if (eor == 1) {
                /* Handle end of record */
                sim_debug(DEBUG_CHAN, &dsk_dev,
                        "Disk chan %d end of track\n\r", chan);
                if ((uptr->u5 & DSKSTA_CMSK) == DVSR &&
                        (uptr->u5 & DSKSTA_XFER) == 0)
                        disk_posterr(uptr, PROG_NOREC);
                uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
                chan_set(chan, DEV_REOR|CTL_END);
            }
            sim_activate(uptr, us_to_ticks(dsk->datarate));
            return SCPE_OK;
        }
    }

    /* Handle reading of data */
    if (chan_test(chan, CTL_READ) && uptr->u5 & DSKSTA_CMD) {
        /* Channel asking for end of record? */
        if (chan_stat(chan, DEV_WEOR)) {
            sim_debug(DEBUG_CHAN, &dsk_dev, "Disk chan %d -> weor\n\r", chan);
            uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
            chan_set(chan, CTL_END|DEV_REOR);
            sim_activate(uptr, us_to_ticks(100));
            return SCPE_OK;
        }
        eor = disk_read(uptr, &ch, chan);
        /* Check if we got error during read */
        if (eor == -1) {
            sim_activate(uptr, us_to_ticks(100));
            return SCPE_OK;
        }
        switch(chan_write_char(chan, &ch, (eor)?DEV_REOR:0)) {
        case TIME_ERROR:
             /* Flag as timming error */
             disk_posterr(uptr, DATA_RESPONSE);
             break;
        case END_RECORD:
             /* Fill the buffer until end of record */
             sim_debug(DEBUG_CHAN, &dsk_dev, "Disk chan %d eor\n\r", chan);
             if ((uptr->u5 & DSKSTA_CMSK) == DVSR &&
                    (uptr->u5 & DSKSTA_XFER) == 0)
                    disk_posterr(uptr, PROG_NOREC);
             uptr->u5 &= ~(DSKSTA_SCAN | DSKSTA_XFER);
             chan_set(chan, CTL_END);
             uptr->u5 |= DSKSTA_DATA;
             break;
        case DATA_OK:
             uptr->u5 |= DSKSTA_DATA;
             break;
        }
        sim_activate(uptr, us_to_ticks(dsk->datarate));
        return SCPE_OK;
    }

    /* Handle read/write without a command */
    if (chan_test(chan, CTL_WRITE|CTL_READ) &&
           (uptr->u3 & 0xff) == cmd_mod[chan] &&
           (uptr->u5 & (DSKSTA_DATA|DSKSTA_CMD)) == 0)
        disk_posterr(uptr, PROG_INVSEQ);

  seeking:
    if (uptr->wait || uptr->u5 & DSKSTA_CMD)
        sim_activate(uptr, us_to_ticks(100));

    /* Handle seeking */
    if (uptr->wait > 0) {
        uptr->wait--;
        if (uptr->wait == 0) {
            sim_debug(DEBUG_EXP, &dsk_dev, "Seek done dev=%d\n", dev);
            sense_unit[schan] |= 1 << unit_bit[dev];
#ifdef I7010
            chan_seek_done[chan] = 1;
#else
            chan9_set_attn(chan, sel);
#endif
        }
    }

    return SCPE_OK;
}

/* Post a error on a given unit. */
void
disk_posterr(UNIT * uptr, uint32 error)
{
    int                 chan;
    int                 schan;
    int                 sel;
    int                 u = (uptr->u3 >> 8) & 0xf;

    uptr = &dsk_unit[u];
    uptr->u5 &= ~(DSKSTA_CMD | DSKSTA_XFER | DSKSTA_SCAN);
    chan = UNIT_G_CHAN(uptr->flags);
    sel = (uptr->flags & UNIT_SELECT) ? 1 : 0;
    schan = (chan * 2) + sel;
    sense[schan] |= error;
    if (error != 0)
        chan9_set_error(chan, SNS_UEND);
    chan_set(chan, DEV_REOR|CTL_END);
    sim_debug(DEBUG_DETAIL, &dsk_dev, "post err dev=%d err=%08x\n", u,error);
#ifdef I7010
    if ((error & STAT_OFFLINE) == STAT_OFFLINE)
        chan_io_status[chan] |= 01;

    if ((error & STAT_NOTRDY) == STAT_NOTRDY)
        chan_io_status[chan] |= 02;

    if (error & (EXPT_FILECHK|EXPT_DSKCHK|DATA_PARITY|DATA_CHECK|
                 DATA_RESPONSE|PROG_INVADDR) & 0xFFFF)
        chan_io_status[chan] |= 04;

    if (error & (PROG_NOREC|PROG_FMTCHK|PROG_INVCODE|PROG_INVSEQ) & 0xffff)
        chan_io_status[chan] |= 010;
#endif
}

/* Post error for command that could not be completed */
void
disk_cmderr(UNIT * uptr, uint32 error)
{
    int                 chan;
    int                 schan;
    int                 sel;
    int                 u = (uptr->u3 >> 8) & 0xf;

    uptr = &dsk_unit[u];
    uptr->u5 &= ~(DSKSTA_CMSK | DSKSTA_CMD | DSKSTA_CHECK | DSKSTA_WRITE);
    chan = UNIT_G_CHAN(uptr->flags);
    sel = (uptr->flags & UNIT_SELECT) ? 1 : 0;
    schan = (chan * 2) + sel;
    sense[schan] |= error;
    if (error != 0)
        chan9_set_error(chan, SNS_UEND);
    chan_set(chan, DEV_REOR|CTL_END);
    sim_debug(DEBUG_DETAIL, &dsk_dev, "cmd err dev=%d err=%08x\n", u,error);
#ifdef I7010
    if ((error & STAT_OFFLINE) == STAT_OFFLINE)
        chan_io_status[chan] |= 01;

    if ((error & STAT_NOTRDY) == STAT_NOTRDY)
        chan_io_status[chan] |= 02;

    if (error & (EXPT_FILECHK|EXPT_DSKCHK|DATA_PARITY|DATA_CHECK|
                 DATA_RESPONSE|PROG_INVADDR) & 0xFFFF)
        chan_io_status[chan] |= 04;

    if (error & (PROG_NOREC|PROG_FMTCHK|PROG_INVCODE|PROG_INVSEQ) & 0xffff)
        chan_io_status[chan] |= 010;
#endif
}

/* Process command */
int
disk_cmd(UNIT * uptr)
{
    uint8               ch;
    UNIT               *base;
    UNIT               *up;
    int                 chan;
    int                 schan;
    int                 sel;
    int                 i;
    int                 u;
    int                 t;
    int                 trk;
    int                 cyl;

    /* Get information on unit */
    u = uptr - dsk_unit;
    base = &dsk_unit[(uptr->u3 >> 8) & 0xf];
    chan = UNIT_G_CHAN(base->flags);
    sel = (base->flags & UNIT_SELECT) ? 1 : 0;
    schan = (chan * 2) + sel;

    /* Check if we have a command yet */
    switch(chan_read_char(chan, &ch, 0)) {
    case TIME_ERROR:
        disk_cmderr(uptr, DATA_RESPONSE);
    case END_RECORD:
        return 0;
    case DATA_OK:
        sim_debug(DEBUG_DATA, &dsk_dev, "unit=%d data=%02o\n", u, ch);
        break;
    }

    /* Place in command buffer */
    switch(cmd_count[chan]) {
    case 0:
    case 1:
        if (ch != 012)
            cmd_buffer[chan] |= (ch & 0xf) << (4 * (1 - cmd_count[chan]));
        break;
    case 2:
    case 3:
        if (ch != 012)
            cmd_mod[chan] |= (ch & 0xf) << (4 * (3 - cmd_count[chan]));
        break;
    case 4:
    case 5:
    case 6:
    case 7:
        if (ch != 012)
           cmd_option[chan] |= (ch & 0xf) << (16 + (4 * (7 - cmd_count[chan])));
        break;
    case 8:
    case 9:
        cmd_option[chan] |= (ch & 0x3f) << (6 * (9 - cmd_count[chan]));
        break;
    }

    /* Need at least two chars to determine command */
    if (++cmd_count[chan] == 1)
        return 1;

    /* Check if we have enough digits */
    switch(cmd_buffer[chan]) {
    case DNOP:          /* Nop */
    case DREL:          /* Release */
    case DEBM:          /* Eight Bit mode */
    case DSBM:          /* Six bit mode */
        break;          /* Yes */

    case DSAI:          /* Set Access Inoperative */
        if (cmd_count[chan] <= 3)
           return 1;    /* Need more */
        break;
    case DSEK:          /* Seek */
    case DWRF:          /* Prepare to Format */
    case DVHA:          /* Prepare to Verify home addr */
        if (cmd_count[chan] <= 7)
           return 1;    /* Need more */
        break;
    case DVTA:          /* Prepare to Verify track addr */
    case DVTN:          /* Prepare to Verify track no addr */
    case DVCY:          /* Prepare to Verify Cyl */
    case DVSR:          /* Prepare to Verify single record */
    case DWRC:          /* Prepare to Write Check */
        if (cmd_count[chan] < 10)
           return 1;
        break;
    }

    /* Flag last item recieved */
    chan_set(chan, DEV_REOR);
    chan9_clear_error(chan, sel);

    sim_debug(DEBUG_CMD, &dsk_dev, "unit=%d cmd=%02x %02x %04x %04o ",u,
         cmd_buffer[chan], cmd_mod[chan], cmd_option[chan] >> 16,
                 cmd_option[chan] & 07777);
    /* 40  36  32   2824  2016     6     0 */
    /* 01   2  3     4 5   6 7    8     9  */
    /* op | ac mod | t t | t t | HA2 | HA2 */
    up = NULL;

    sense[schan] &= STAT_SIXBIT;
    switch (cmd_buffer[chan]) {
    case DNOP:          /* Nop */
    case DREL:          /* Release */
         /* Should not happen, but if read or write, cpy will be
          * expected so turn on command flag to catch disconnect */
         sim_debug(DEBUG_CMD, &dsk_dev, "nop\n");
         goto clear_drive;

    case DEBM:          /* Eight Bit mode */
         /* Should not happen, but if read or write, cpy will be
          * expected so turn on command flag to catch disconnect */
         sim_debug(DEBUG_CMD, &dsk_dev, "eight bit mode\n");
         sense[schan] &= ~STAT_SIXBIT;
         goto clear_drive;

     case DSBM:         /* Six bit mode */
         /* Should not happen, but if read or write, cpy will be
          * expected so turn on command flag to catch disconnect */
         sim_debug(DEBUG_CMD, &dsk_dev, "six bit mode\n");
         sense[schan] |= STAT_SIXBIT;
clear_drive:
        /* Scan all units and clear command */
        for (i = 0; i < NUM_DEVS_DSK; i++) {
            int                 xchan = UNIT_G_CHAN(dsk_unit[i].flags);
            int                 j;

            if (xchan != chan)
                continue;
            for (j = 3 * NUM_DEVS_DSK + i; j >= 0; j -= NUM_DEVS_DSK)
                dsk_unit[j].u5 &= ~ DSKSTA_CMSK;
        }
        sim_activate(uptr, us_to_ticks(100));
        return 1;

     case DWRC:         /* Prepare to Write Check */
         sim_debug(DEBUG_CMD, &dsk_dev, "write check\n");
     case DVSR:         /* Prepare to Verify single record */
     case DWRF:         /* Prepare to Format */
     case DVTN:         /* Prepare to Verify track no addr */
     case DVCY:         /* Prepare to Verify Cyl */
     case DVTA:         /* Prepare to Verify track addr */
     case DVHA:         /* Prepare to Verify home addr */
     case DSAI:         /* Set Access Inoperative */
     case DSEK:         /* Seek */
            break;      /* Go find unit to operate on */
     default:
            sim_debug(DEBUG_CMD, &dsk_dev, " Unknown Command\n\r");
            /* Flag as invalid command */
            disk_cmderr(uptr, PROG_INVCODE);
            return 1;
     }

     /* Find actual owner of this command */
     for (i = 0; i < NUM_DEVS_DSK; i++) {

        if ((dsk_unit[i].flags & (UNIT_SELECT | UNIT_CHAN)) !=
            (base->flags & (UNIT_SELECT | UNIT_CHAN)))
            continue;

        /* Adjust for unit */
        if ((0xff & dsk_unit[i].u3) == cmd_mod[chan])
            up = &dsk_unit[i];
        else if ((0xff & dsk_unit[i + NUM_DEVS_DSK].u3) == cmd_mod[chan])
            up = &dsk_unit[i + NUM_DEVS_DSK];
        else if ((0xff & dsk_unit[i + (NUM_DEVS_DSK * 2)].u3) == cmd_mod[chan])
            up = &dsk_unit[i + (NUM_DEVS_DSK * 2)];
        else if ((0xff & dsk_unit[i + (NUM_DEVS_DSK * 3)].u3) == cmd_mod[chan])
            up = &dsk_unit[i + (NUM_DEVS_DSK * 3)];
        else
            continue;

        /* Check if unit is busy */
        if (cmd_buffer[chan] != DWRC && (up->u5 & DSKSTA_CMD || up->wait > 0)) {
            sim_debug(DEBUG_CMD, &dsk_dev, "unit=%d busy\n", u);
            if (up->wait > 5)
                up->wait = 5;
            disk_cmderr(uptr, STAT_NOTRDY);
            return 1;
        } else {
            if (cmd_buffer[chan] == DWRC) {
                if (up->u5 & DSKSTA_CMSK) {
                    up->u5 |= DSKSTA_CHECK;
                    up->u5 &= ~DSKSTA_DATA;
                    cmd_buffer[chan] = up->u5 & DSKSTA_CMSK;
                } else {
                    disk_cmderr(up, PROG_INVSEQ);
                    return 0;
                }
            } else {
                up->u5 &= ~(DSKSTA_CMSK|DSKSTA_CHECK|DSKSTA_WRITE|DSKSTA_DATA);
                up->u5 |= cmd_buffer[chan];
            }
            break;
        }
    }

    if (up == NULL)  {
        sim_debug(DEBUG_CMD, &dsk_dev, "invalid unit\n");
        /* Flag as invalid command */
        disk_cmderr(uptr, STAT_OFFLINE);
        return 1;
    }

    /* Adjust to new unit */
    u = up - dsk_unit;
    base = &dsk_unit[(up->u3 >> 8) & 0xf];
    chan = UNIT_G_CHAN(base->flags);
    sel = (base->flags & UNIT_SELECT) ? 1 : 0;
    schan = (chan * 2) + sel;
    /* Clear unit attention */
    sense_unit[schan] &= ~(1 << unit_bit[up->u3 & 0xff]);

    /* Check if there is a unit here */
    if ((base->flags & UNIT_ATT) == 0) {
         disk_cmderr(uptr, STAT_OFFLINE);
         return 1;
    }

    /* Find out track and cylinder of this operation */
    trk = bcd_to_track(cmd_option[chan]);

    if (chan_test(chan, CTL_PWRITE))
        sim_debug(DEBUG_CMD, &dsk_dev, "write ");
    if (chan_test(chan, CTL_PREAD))
        sim_debug(DEBUG_CMD, &dsk_dev, "read ");
    /* Do command */
    switch (cmd_buffer[chan]) {
    case DSAI:          /* Set Access Inoperative */
        detach_unit(base);
        disk_cmderr(up, 0);
        return 1;

    case DSEK:          /* Seek */
        cyl = trk / disk_type[base->u4].track;
        /* Check how far we have to move */
        t = cyl - arm_cyl[u];
        if (t < 0)
            t = -t;
        sim_debug(DEBUG_CMD, &dsk_dev,
                  "DSEK unit=%d %d cylinders to %d trk=%d\n", u, t, cyl, trk);
        up->u5 &= ~(DSKSTA_CMSK | DSKSTA_CMD | DSKSTA_CHECK | DSKSTA_WRITE);
        arm_cyl[u] = cyl;       /* And cylinder */
        if (cyl > disk_type[base->u4].cyl) {
            disk_cmderr(up, PROG_INVADDR);
            return 1;
        }
        /* Read in track buffer */
        disk_rblock(up, trk);

        /* Set up for seek wait */
        up->wait = 0;
        /* From documentation, it looks like seeks were a fixed time
         * based on movement between cylinder groups */
        if (t == 0)     /* At cylinder, give attention */
            up->wait = 2;       /* Electronic select time */
        else if (t > 50)
            up->wait = (1800);
        else if (t > 10)
            up->wait = (1200);
        else
            up->wait = (300);
        break;

    case DWRF:          /* Prepare to Format */
        /* Verify ok to format */
        if ((base->flags & FORMAT_OK) == 0) {
            disk_cmderr(uptr, PROG_FMTCHK);
            return 1;
        }

        cyl = trk / disk_type[base->u4].track;

        /* Make sure positioned to correct track */
        if (arm_cyl[u] != cyl) {
            disk_cmderr(uptr, PROG_INVSEQ);
            return 1;
        }

        /* Make sure head on valid cylinder */
        if (cyl > disk_type[base->u4].cyl) {
            disk_cmderr(up, PROG_INVADDR);
            return 1;
        }

        fmt_cyl[u] = cyl;
        sim_debug(DEBUG_CMD, &dsk_dev, "FMT unit=%d\n", u);
        up->u5 |= DSKSTA_SCAN | DSKSTA_CMD | DSKSTA_WRITE; /* Flag as write */
        up->u6 = 0;
        chan_set(chan, DEV_SEL);
        break;

    case DVHA:          /* Prepare to Verify home addr */
        /* Verify HA2 ok to write */
        if ((base->flags & HA2_OK) == 0) {
            disk_cmderr(up, PROG_FMTCHK);
            return 1;
        }
        /* fall through */
    case DVTA:          /* Prepare to Verify track addr */
    case DVTN:          /* Prepare to Verify track no addr */
    case DVCY:          /* Prepare to Verify Cyl */
        switch (cmd_buffer[chan]) {
        case DVTA:
            sim_debug(DEBUG_CMD, &dsk_dev, "DVTA unit=%d ", u);
            break;
        case DVTN:
            sim_debug(DEBUG_CMD, &dsk_dev, "DVTN unit=%d ", u);
            break;
        case DVCY:
            sim_debug(DEBUG_CMD, &dsk_dev, "DVCY unit=%d ", u);
            break;
        case DVHA:
            sim_debug(DEBUG_CMD, &dsk_dev, "DVHA unit=%d ", u);
            break;
        }

        sim_debug(DEBUG_CMD, &dsk_dev, "trk=%d\n\r", trk);

        /* Make sure head on valid cylinder */
        if ((trk / disk_type[base->u4].track) > disk_type[base->u4].cyl) {
            disk_cmderr(up, PROG_INVADDR);
            return 1;
        }

        /* Make sure we have correct format for this cylinder in buffer */
        disk_rblock(up, trk);
        /* Start actual operations */
        up->u5 |= DSKSTA_SCAN | DSKSTA_CMD;
        up->u6 = 0;
        chan_set(chan, DEV_SEL);
        break;

    case DVSR:          /* Prepare to Verify single record */
        /* Start actual operations */
        up->u5 |= DSKSTA_SCAN | DSKSTA_CMD;
        up->u6 = 0;
        chan_set(chan, DEV_SEL);
        break;
    }
    sim_activate(up, us_to_ticks(50));
    return 0;
}

/* Print a format pattern */
void
print_format(UNIT * uptr)
{
    struct disk_t      *dsk = &disk_type[uptr->u4];
    int                 i, j;
    int                 u = uptr - dsk_unit;
    int                 flag;
    int                 lflag = -1;

    sim_debug(DEBUG_DETAIL, &dsk_dev, "unit=%d (%s) format: ", u, dsk->name);

    i = 0;
    j = 0;
    do {
        flag = fbuffer[u][i / 4];
        flag >>= (i & 03) * 2;
        flag &= 03;
        if (lflag != flag) {
           if (j != 0) {
                switch(lflag) {
                case FMT_DATA:
                     sim_debug(DEBUG_DETAIL, &dsk_dev, "DA(%d) ", j);
                     break;
                case FMT_HDR:
                     sim_debug(DEBUG_DETAIL, &dsk_dev, "RA(%d) ", j);
                     break;
                case FMT_HA2:
                     sim_debug(DEBUG_DETAIL, &dsk_dev, "HA2(%d) ", j);
                     break;
                }
           }
           j = 1;
           lflag = flag;
        } else {
           j++;
        }
        i++;
    } while (flag != FMT_END);

    sim_debug(DEBUG_DETAIL, &dsk_dev, "total=%d\n", i);
}


int
disk_rblock(UNIT * uptr, int trk)
{
    int                 u = uptr - dsk_unit;
    struct disk_t      *dsk = &disk_type[uptr->u4];
    UNIT               *base = &dsk_unit[(uptr->u3 >> 8) & 0xf];
    FILE               *f = base->fileref;
    int                 offset = 0;
    int                 fbase = 0;

    offset = dsk->cyl * dsk->track * dsk->bpt;
    fbase = dsk->fmtsz;
    offset *= u / (NUM_DEVS_DSK);
    fbase *= u / (NUM_DEVS_DSK);
    offset += dsk->fmtsz * dsk->mods * dsk->arms;

    if (uptr->u5 & DSKSTA_DIRTY) {
        disk_wblock(uptr);
    }

    if (arm_cyl[u] != fmt_cyl[u]) {
        (void)sim_fseek(f, fbase + arm_cyl[u] * dsk->fbpt, SEEK_SET);
        (void)sim_fread(fbuffer[u], 1, dsk->fbpt, f);
        fmt_cyl[u] = arm_cyl[u];
        print_format(uptr);
    }
    /* Read in actualy track data */
    if (dtrack[u] != trk) {
        sim_debug(DEBUG_DETAIL, &dsk_dev, "unit=%d Read track %d\n", u,
                  trk);
        (void)sim_fseek(f, offset + trk * dsk->bpt, SEEK_SET);
        if (sim_fread(dbuffer[u], 1, dsk->bpt, f) != dsk->bpt)
            memset(dbuffer[u], 0, dsk->bpt);
        dtrack[u] = trk;
    }
    return 1;
}

int
disk_wblock(UNIT * uptr)
{
    int                 u = uptr - dsk_unit;
    struct disk_t      *dsk = &disk_type[uptr->u4];
    UNIT               *base = &dsk_unit[(uptr->u3 >> 8) & 0xf];
    FILE               *f = base->fileref;
    int                 offset = 0;

    offset = dsk->cyl * dsk->track * dsk->bpt;
    offset *= u / (NUM_DEVS_DSK);
    offset += dsk->fmtsz * dsk->mods * dsk->arms;

    /* Check if new format data */
    if ((uptr->u5 & DSKSTA_CMSK) == DWRF) {
        if (uptr->u5 & (DSKSTA_CHECK|DSKSTA_DIRTY)) {
            switch (disk_format(uptr, f, arm_cyl[u], base)) {
            case 2:
                if (uptr->u5 & DSKSTA_CHECK) {
                    disk_posterr(uptr, PROG_FMTCHK|EXPT_DSKCHK);
                    break;
                }
            case 1:
                disk_posterr(uptr, PROG_FMTCHK);
                break;
            case 0:
                break;
            }
            uptr->u5 &= ~(DSKSTA_CHECK);
        }
        return 1;
    }

    if (uptr->u5 & DSKSTA_CHECK) {
        uptr->u5 &= ~(DSKSTA_CHECK);
        if ((uptr->u5 & DSKSTA_DIRTY) == 0)
            return 1;
    } else if ((uptr->u5 & DSKSTA_DIRTY) == 0)
        return 1;

    sim_debug(DEBUG_DETAIL, &dsk_dev, "unit=%d Write track %d\n",
              u, dtrack[u]);
    /* Write in actualy track data */
    (void)sim_fseek(f, offset + dtrack[u] * dsk->bpt, SEEK_SET);
    (void)sim_fwrite(dbuffer[u], 1, dsk->bpt, f);
    uptr->u5 &= ~DSKSTA_DIRTY;
    return 1;
}

/* Convert a format pattern into a format track */
int
disk_format(UNIT * uptr, FILE * f, int cyl, UNIT * base)
{
    uint8               tbuffer[MAXTRACK];
    struct disk_t      *dsk = &disk_type[uptr->u4];
    int                 i, j;
    int                 out = 0;
    int                 u = uptr - dsk_unit;
    uint8               ch;
    int                 offset;

    f = base->fileref;

    offset = dsk->fmtsz;
    offset *= u / (NUM_DEVS_DSK);

    uptr->u5 &= ~(DSKSTA_DIRTY);
    sim_debug(DEBUG_DETAIL, &dsk_dev, "unit=%d (%s) format: ", u, dsk->name);
    /* Scan over new format */
    /* Convert format specification in place in dbuffer, then
     * pack it into fbuffer and write to file */

    /* Skip initial gap */
    for (i = 0; i < MAXTRACK && dbuffer[u][i] == 04; i++) ;
    if (i == MAXTRACK)
        return 2;               /* Failed if we hit end */
    /* HA1 Gap */
    for (j = i; i < MAXTRACK && dbuffer[u][i] == 03; i++) ;
    if ((i - j) > 12)
        return 1;               /* HA1 too big */

    if (dbuffer[u][i++] != 04)
        return 2;               /* Not gap */
    for (j = i; i < MAXTRACK && dbuffer[u][i] == 03; i++) ;
    if (i == MAXTRACK)
        return 2;               /* Failed if we hit end */

    if (dbuffer[u][i++] != 04)
        return 2;               /* Not gap */

    /* Size up HA2 gap */
    for (j = i;
         i < MAXTRACK && (dbuffer[u][i] == 03 || dbuffer[u][i] == 01);
         i++) ;
    j = i - j;
    if (j < 6)
        return 2;

    j -= dsk->overhd;           /* Remove overhead */
    sim_debug(DEBUG_DETAIL, &dsk_dev, "HA2(%d) ", j);
    for (; j > 0; j--)
        tbuffer[out++] = FMT_HA2;
    /* Now grab records */
    while (i < MAXTRACK) {
        ch = dbuffer[u][i++];
        if (ch == 0x40)
            break;              /* End of record */
        if (ch != 04 && ch != 02)
            return 2;           /* Not a gap */

        for (j = i; i < MAXTRACK && dbuffer[u][i] == ch; i++) ;
        ch = dbuffer[u][i];     /* Should be RA */
        /* Gap not long enough or eor */
        if (ch == 0x40 || (i - j) < 11)
            break;

        /* Size up RA gap */
        if (ch != 01 && ch != 03)
            return 1;           /* Not header */
        for (j = i; i < MAXTRACK && dbuffer[u][i] == ch; i++) ;
        j = i - j;
        if (j < 10)
            return 2;
        j -= dsk->overhd;       /* Remove overhead */
        sim_debug(DEBUG_DETAIL, &dsk_dev, "RA(%d) ", j);
        for (; j > 0; j--)
            tbuffer[out++] = FMT_HDR;
        ch = dbuffer[u][i++];
        if (ch == 0x40)
            break;              /* End of record */
        if (ch != 04 && ch != 02)
            return 1;           /* End of RA Field */
        ch = dbuffer[u][i];
        if (ch != 01 && ch != 03)
            return 1;           /* Not gap */
        for (j = i; i < MAXTRACK && dbuffer[u][i] == ch; i++) ;
        if ((i - j) < 10)
            return 2;           /* Gap not large enough */
        ch = dbuffer[u][i++];
        if (ch != 04 && ch != 02)
            return 2;           /* End of RA Gap */
        ch = dbuffer[u][i];     /* Should be DA */
        if (ch == 0x40)
            break;              /* End of record */
        if (ch != 01 && ch != 03)
            return 1;           /* Not Record data */
        for (j = i; i < MAXTRACK && dbuffer[u][i] == ch; i++) ;
        j = i - j;
        if (j < 10)
            return 2;
        j -= dsk->overhd;       /* Remove overhead */
        sim_debug(DEBUG_DETAIL, &dsk_dev, "DA(%d) ", j);
        for (; j > 0; j--)
            tbuffer[out++] = FMT_DATA;
    }
    sim_debug(DEBUG_DETAIL, &dsk_dev, "total=%d\n", out);
    /* If checking, don't update */
    if (uptr->u5 & DSKSTA_CHECK)
        return 0;

    /* Put four END chars to end of buffer */
    for (j = 4; j > 0; j--)
        tbuffer[out++] = FMT_END;

    /* Now grab every four characters and place them in next format location */
    for (j = i = 0; j < out; i++) {
        uint8               temp;

        temp = (tbuffer[j++] & 03);
        temp |= (tbuffer[j++] & 03) << 2;
        temp |= (tbuffer[j++] & 03) << 4;
        temp |= (tbuffer[j++] & 03) << 6;
        fbuffer[u][i] = temp;
        if (i > dsk->fbpt)
           break;
    }
    fbuffer[u][dsk->fbpt-1] = (FMT_END<<6)|(FMT_END<<4)|(FMT_END<<2)|FMT_END;

    /* Now write the buffer to the file */
    (void)sim_fseek(f, offset + cyl * dsk->fbpt, SEEK_SET);
    (void)sim_fwrite(fbuffer[u], 1, dsk->fbpt, f);

    /* Make sure we did not pass size of track */
    if (out > (int)dsk->bpt)
        return 1;               /* Too big for track */
    return 0;
}

/* Handle writing of one character to disk */
int
disk_write(UNIT * uptr, uint8 data, int chan, int eor)
{
    int                 u = uptr - dsk_unit;
    UNIT               *base = (u > NUM_DEVS_DSK) ? &uptr[-NUM_DEVS_DSK] : uptr;
    int                 skip = 1;
    int                 flag = -1;
    uint8               cmd;
    int                 schan;

    schan = (chan * 2) + ((base->flags & UNIT_SELECT) ? 1 : 0);
    cmd = (uptr->u5) & DSKSTA_CMSK;
    if (uptr->u6 == 0 && cmd != DWRF) {
        int     cyl = dtrack[u] / disk_type[base->u4].track;

        if (arm_cyl[u] != cyl) {
            sim_debug(DEBUG_CMD, &dsk_dev, "cyl not equal %d %d %d\n\r",
                        u, arm_cyl[u], cyl);
            disk_posterr(uptr, PROG_INVADDR);
            return -1;
        }

        /* Verify that the home address matches */
        if (cmd != DVHA && cmd != DVSR) {
            uint16      t = cmd_option[chan] & 01717;
            uint16      ha;
            ha = (077 & dbuffer[u][0]) << 6;
            ha |= 077 & dbuffer[u][1];
            /* Mask out bits we ignore */
            ha &= 01717;
            /* Convert BCD 0 to Binary 0 */
            if ((ha & 01700) == 01200)
                ha &= 077;
            if ((ha & 017) == 012)
                ha &= 07700;
            if ((t & 01700) == 01200)
                t &= 077;
            if ((t & 017) == 012)
                t &= 07700;

            sim_debug(DEBUG_CMD, &dsk_dev, "HA %04o(c) %04o(d)\n", t, ha);
            if (ha != t) {
                disk_posterr(uptr, PROG_NOREC);
                return -1;
            }
        } else {
            sim_debug(DEBUG_CMD, &dsk_dev, "HA  ignored\n");
        }
    }

    while (skip) {
        flag = fbuffer[u][uptr->u6 / 4];
        flag >>= (uptr->u6 & 03) * 2;
        flag &= 03;
        switch (uptr->u5 & DSKSTA_CMSK) {
        case DWRF:              /* Format */
            if ((uint32)uptr->u6 > disk_type[uptr->u4].bpt) {
                return 1;
            }
            if (uptr->u5 & DSKSTA_CHECK) {
                if ((dbuffer[u][uptr->u6++] & 077) != (data & 077)) {
                    disk_posterr(uptr, DATA_CHECK);
                    return -1;
                }
            } else {
                dbuffer[u][uptr->u6++] = data;
                uptr->u5 |= DSKSTA_DIRTY;
            }
            return 0;
        case DVTN:              /* Verify track no addr */
            if (flag == FMT_END) {
                return 1;
            }
            if (flag == FMT_DATA)
                skip = 0;
            else
                uptr->u6++;
            break;
        case DVTA:              /* Verify track addr */
            if (flag == FMT_END) {
                return 1;
            }
            if (flag != FMT_HA2)
                skip = 0;
            else
                uptr->u6++;
            break;
        case DVHA:              /* Verify home addr */
            if (flag == FMT_END) {
                return 1;
            }
            skip = 0;
            break;
        case DVCY:              /*  Verify Cyl */
            if (flag == FMT_END) {
                /* Move to next track */
                int                 trk = dtrack[u];
                int                 cyl = trk / disk_type[uptr->u4].track;

                uptr->u6 = 0;
                if (eor)
                    return 1;

                if ((++trk) / disk_type[uptr->u4].track != cyl)
                    return 1;
                disk_rblock(uptr, trk);
            } else if (flag != FMT_DATA)
                uptr->u6++;
            else
                skip = 0;
            break;
        case DVSR:              /* Verify single record */
            if (flag == FMT_DATA && uptr->u5 & DSKSTA_XFER) {
                skip = 0;
            } else if (uptr->u5 & DSKSTA_XFER) {
                /* Not in a Data record, and we were transfering, all done */
                uptr->u5 &= ~DSKSTA_XFER;
                uptr->u6 = 0;
                return 1;
            } else if (flag == FMT_END) {
                /* If we hit the end, then no record found */
                disk_posterr(uptr, PROG_NOREC);
                return -1;
            } else if (flag == FMT_HDR) {
                uint8               ch;
                uint32              match = 0;
                int                 i;

                for (i = 0; i < 4 && flag == FMT_HDR; i++) {
                    ch = dbuffer[u][uptr->u6++];
                    match <<= 4;
                    if (ch != 012)
                        match |= ch & 0xf;
                    flag = fbuffer[u][uptr->u6 / 4];
                    flag >>= (uptr->u6 & 03) * 2;
                    flag &= 03;
                }
                /* Short header, skip it, but should not have been made */
                if (flag != FMT_HDR)
                    break;
                match <<= 16;
                /* Grab last two Characters */
                while (flag == FMT_HDR) {
                    ch = dbuffer[u][uptr->u6++];
                    match = (match & 0xFFFF0000) | ((match & 0x3f) << 6) |
                                 (ch & 077);
                    flag = fbuffer[u][uptr->u6 / 4];
                    flag >>= (uptr->u6 & 03) * 2;
                    flag &= 03;
                }
                if (flag != FMT_DATA)
                    break;
                /* Compare values. */
                if (match != cmd_option[chan])
                    break;
                /* Got a match, exit loop */
                uptr->u5 &= ~DSKSTA_SCAN;
                uptr->u5 |= DSKSTA_XFER;
                skip = 0;
            } else {
                uptr->u6++;
            }
            break;
        }
    }
    data &= (sense[schan] & STAT_SIXBIT)?077:0277;
    if (uptr->u5 & DSKSTA_CHECK) {
        if (dbuffer[u][uptr->u6++] != data) {
            sim_printf("Mismatch %d %03o != %03o\n\r",
                   uptr->u6-1, dbuffer[u][uptr->u6-1], data);
            disk_posterr(uptr, DATA_CHECK);
        }
    } else {
        dbuffer[u][uptr->u6++] = data;
        uptr->u5 |= DSKSTA_DIRTY;
    }
    return 0;
}

/* Handle reading of one character to disk */
int
disk_read(UNIT * uptr, uint8 * data, int chan)
{
    int                 u = uptr - dsk_unit;
    UNIT               *base = (u > NUM_DEVS_DSK) ? &uptr[-NUM_DEVS_DSK] : uptr;
    int                 skip = 1;
    int                 flag;
    uint8               cmd;
    int                 schan;

    schan = (chan * 2) + ((base->flags & UNIT_SELECT) ? 1 : 0);
    cmd = (uptr->u5) & DSKSTA_CMSK;
    if (uptr->u6 == 0) {
        int     cyl = dtrack[u] / disk_type[base->u4].track;
        if (arm_cyl[u] != cyl) {
            disk_posterr(uptr, PROG_INVADDR);
            return -1;
        }

        /* Verify that the home address matches */
        if (cmd != DVHA && cmd != DVSR) {
            uint16      t = cmd_option[chan] & 01717;
            uint16              ha;
            ha = (077 & dbuffer[u][0]) << 6;
            ha |= 077 & dbuffer[u][1];
            /* Mask out bits we ignore */
            ha &= 01717;
            /* Convert BCD 0 to Binary 0 */
            if ((ha & 07700) == 01200)
                ha &= 077;
            if ((ha & 077) == 012)
                ha &= 07700;
            if ((t & 07700) == 01200)
                t &= 077;
            if ((t & 077) == 012)
                t &= 07700;

            sim_debug(DEBUG_CMD, &dsk_dev, "HA %04o(c) %04o(d)\n", t, ha);
            if (ha != t) {
                disk_posterr(uptr, PROG_NOREC);
                return -1;
            }
        } else {
            sim_debug(DEBUG_CMD, &dsk_dev, "HA ignored\n");
        }
    }

    while (skip) {
        flag = fbuffer[u][uptr->u6 / 4];
        flag >>= (uptr->u6 & 03) * 2;
        flag &= 03;
        switch (cmd) {
        case DWRF:              /* Format */
            disk_posterr(uptr, PROG_FMTCHK);
            return 1;
        case DVTN:              /* Verify track no addr */
            if (flag == FMT_END) {
                uptr->u6 = 0;
                return 1;
            }
            if (flag == FMT_DATA)
                skip = 0;
            else
                uptr->u6++;
            break;
        case DVTA:              /* Verify track addr */
            if (flag == FMT_END) {
                uptr->u6 = 0;
                return 1;
            }
            if (flag != FMT_HA2)
                skip = 0;
            else
                uptr->u6++;
            break;
        case DVHA:              /* Prepare to Verify home addr */
            if (flag == FMT_END) {
                uptr->u6 = 0;
                return 1;
            }
            skip = 0;
            break;
        case DVCY:              /* Verify Cyl */
            if (flag == FMT_END) {
                /* Move to next track */
                int                 trk = dtrack[u];
                UNIT               *base =
                    (u > NUM_DEVS_DSK) ? &uptr[-NUM_DEVS_DSK] : uptr;
                int                 cyl = trk / disk_type[base->u4].track;

                uptr->u6 = 0;
                if ((++trk) / disk_type[base->u4].track != cyl)
                    return 1;
                disk_rblock(uptr, trk);
            } else if (flag != FMT_DATA)
                uptr->u6++;
            else
                skip = 0;
            break;
        case DVSR:              /* Verify single record */
            if (flag == FMT_DATA && uptr->u5 & DSKSTA_XFER) {
                skip = 0;
            } else if (uptr->u5 & DSKSTA_XFER) {
                /* Not in a Data record, and we were transfering, all done */
                uptr->u5 &= ~DSKSTA_XFER;
                uptr->u6 = 0;
                return 1;
            } else if (flag == FMT_END) {
                /* If we hit the end, then no record found */
                disk_posterr(uptr, PROG_NOREC);
                return -1;
            } else if (flag == FMT_HDR) {
                uint8               ch;
                uint32              match = 0;
                int                 i;

                for (i = 0; i < 4 && flag == FMT_HDR; i++) {
                    ch = dbuffer[u][uptr->u6++];
                    match <<= 4;
                    if (ch != 012)
                        match |= ch & 0xf;
                    flag = fbuffer[u][uptr->u6 / 4];
                    flag >>= (uptr->u6 & 03) * 2;
                    flag &= 03;
                }
                /* Short header, skip it, but should not have been made */
                if (flag != FMT_HDR)
                    break;
                match <<= 16;
                /* Grab last two Characters */
                while (flag == FMT_HDR) {
                    ch = dbuffer[u][uptr->u6++];
                    match = (match & 0xFFFF0000) | ((match & 0x3f) << 6) |
                                 (ch & 077);
                    flag = fbuffer[u][uptr->u6 / 4];
                    flag >>= (uptr->u6 & 03) * 2;
                    flag &= 03;
                }
                if (flag != FMT_DATA)
                    break;
                /* Compare values. */
                if (match != cmd_option[chan])
                    break;
                /* Got a match, exit loop */
                uptr->u5 &= ~DSKSTA_SCAN;
                uptr->u5 |= DSKSTA_XFER;
                skip = 0;
            } else {
                uptr->u6++;
            }
            break;
        }
    }
    *data = dbuffer[u][uptr->u6++] & ((sense[schan] & STAT_SIXBIT)?077:0277);
    /* Check if character is last in record */
    flag = fbuffer[u][uptr->u6 / 4];
    flag >>= (uptr->u6 & 03) * 2;
    flag &= 03;
    switch (cmd) {
    case DVTN:          /* Verify track no addr */
    case DVTA:          /* Verify track addr */
    case DVHA:          /* Prepare to Verify home addr */
         if (flag == FMT_END) {
             sim_debug(DEBUG_DATA, &dsk_dev, "eor\n");
             return 1;
         }
         break;
    case DVCY:          /* Verify Cyl */
         if (flag == FMT_END) {
              /* Move to next track */
             UNIT               *base =
                    (u > NUM_DEVS_DSK) ? &uptr[-NUM_DEVS_DSK] : uptr;

            if (((dtrack[u]+1) / disk_type[base->u4].track) !=
                  (dtrack[u] / disk_type[base->u4].track)) {
                  sim_debug(DEBUG_DATA, &dsk_dev, "eor\n");
                  return 1;
            }
         }
         break;
    case DVSR:          /* Verify single record */
         if (flag != FMT_DATA) {
             sim_debug(DEBUG_DATA, &dsk_dev, "eor\n");
             return 1;
         }
    }
    return 0;
}

/* Convert BCD track address to binary address */
int
bcd_to_track(uint32 addr)
{
    int                 trk = 0;
    int                 i;

    for (i = 28; i >= 16; i -= 4) {
        trk = (trk * 10) + ((addr >> i) & 0xf);
    }
    return trk;
}

/* Boot disk. Build boot card loader in memory and transfer to it */
t_stat
dsk_boot(int unit_num, DEVICE * dptr)
{
#ifdef I7090
    UNIT               *uptr = &dptr->units[unit_num];
    int                 chan = UNIT_G_CHAN(uptr->flags) - 1;
    int                 sel = (uptr->flags & UNIT_SELECT) ? 1 : 0;
    int                 dev = uptr->u3 & 0xff;
    int                 msk = (chan / 2) | ((chan & 1) << 11);
    extern uint16       IC;

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

    if (dev == 0)
        dev = 012;
    if (uptr->flags & CTSS_BOOT) {
         /* Build CTSS boot program in memory */
         /* Read first cylinder into B-Core */
         M[0] = 0377777000100LL;      /*  IORT    BOTTOM,,-1  */
         M[1] = 0006000000001LL;      /*   TCOA    *        */
         M[2] = 0007400400100LL;      /* START  TSX     ENTER,4 */
         M[0100] = 0076000000350LL;  /* ENTER  RICU          */
         M[0100] |= (chan + 1) << 9;
         M[0101] = 0054000000120LL;   /*      RSCU    READ   */
         M[0101] |= ((t_uint64) (msk)) << 24;
         M[0102] = 0006000000102LL;   /*      TCOU    *      */
         M[0102] |= ((t_uint64) (chan)) << 24;
         M[0103] = 0476100000042LL;   /*      SEB            */
         M[0104] = 0450000000000LL;   /*      CAL     0      */
         M[0105] = 0036100477777LL;   /*      ACL     32767,4 */
         M[0106] = 0200001400105LL;   /*      TIX     *-1,4,1 */
         M[0107] = 0476100000041LL;   /*      SEA            */
         M[0110] = 0032200000131LL;   /*      ERA     CHKSUM */
         M[0111] = 0450100000046LL;   /*      ORA     ULOC   */
         M[0112] = 0010000000132LL;   /*      TZE     EXIT   */
         M[0113] = 0000000000002LL;   /*      HTR     START  */
         M[0114] = 0101212001212LL;
         M[0114] |= ((t_uint64) (dev)) << 12;
         M[0115] = 0121212121212LL;
         M[0116] = 0100512001212LL;
         M[0116] |= ((t_uint64) (dev)) << 12;
         M[0117] = 0121267671212LL;
         M[0120] = 0700000000004LL;   /*  READ   SMS     4      */
         M[0120] |= sel;
         M[0121] = 0200000000114LL;   /*         CTL     SEEK   */
         M[0122] = 0500000200122LL;   /*         TCM     *,,0   */
         M[0123] = 0200000200116LL;   /*         CTLR    CYLOP  */
         M[0124] = 0400007000125LL;   /*         CPYP    *+1,,N */
         IC = 02;
    } else {
         /* Build IBSYS Boot program in memory */
         M[0] = 0000025000101LL;        /*      IOCD RSCQ,,21 */
         M[1] = 0006000000001LL;        /*      TCOA * */
         M[2] = 0002000000101LL;        /*      TRA RSCQ */

         M[0101] = 0054000000115LL;     /* RSCQ RSCC SMSQ  Mod */
         M[0101] |= ((t_uint64) (msk)) << 24;
         M[0102] = 0064400000000LL;     /* SCDQ SCDC 0  Mod */
         M[0102] |= ((t_uint64) (msk)) << 24;
         M[0103] = 0044100000000LL;     /*      LDI 0 */
         M[0104] = 0405400007100LL;     /*      LFT 7100 */
         M[0105] = 0002000000110LL;     /*      TRA *+3 */
         M[0106] = 0006000000102LL;     /* TCOQ TCOC SCDQ  Mod */
         M[0106] |= ((t_uint64) (chan)) << 24;
         M[0107] = 0002000000003LL;     /*      TRA 3    Enter IBSYS */
         M[0110] = 0076000000350LL;     /* RICQ RICC **    Mod */
         M[0110] |= (chan + 1) << 9;
         M[0111] = 0500512001212LL;     /*LDVCY DVCY  Mod */
         M[0111] |= ((t_uint64) (dev)) << 12;
         M[0112] = 0121222440000LL;     /*      *    */
         M[0113] = 0501212001212LL;     /*LDSEK DSEEK  Mod */
         M[0113] |= ((t_uint64) (dev)) << 12;
         M[0114] = 0121200000000LL;     /*      *  */
         M[0115] = 0700000000016LL;     /* SMSQ SMS   14 */
         M[0115] |= sel;
         M[0116] = 0200000000113LL;     /*      CTL   LDSEK */
         M[0117] = 0500000200117LL;     /*      TCM   *,,, */
         M[0120] = 0200000200111LL;     /*      CTLR  LDVCY */
         M[0121] = 0400001000122LL;     /*      CPYP  *+1,,1 */
         M[0122] = 0000000000122LL;     /*      WTR * */
         M[0123] = 0100000000121LL;     /*      TCH  *-2 */
         M[0124] = 0500000000000LL;     /*      CPYD  0,,0 */
         M[0125] = 0340000000125LL;     /*      TWT   * */
         IC = 0101;
     }

    return SCPE_OK;
#else
    return SCPE_NOFNC;
#endif
}

void
dsk_ini(UNIT * uptr, t_bool f)
{
    uptr->u5 = 0;
}

t_stat
dsk_reset(DEVICE * dptr)
{
    int                 i;
    int                 t;

    for (i = 0; i < NUM_CHAN; i++) {
        sense[i * 2] = sense[i * 2 + 1] = STAT_SIXBIT;
        sense_unit[i * 2] = sense_unit[i * 2 + 1] = 0;
    }
    for (i = 0; i < NUM_DEVS_DSK; i++) {
        dtrack[i] = 077777;
        fmt_cyl[i] = 077777;
        arm_cyl[i] = 0;
        dtrack[i + NUM_DEVS_DSK] = 077777;
        fmt_cyl[i + NUM_DEVS_DSK] = 077777;
        arm_cyl[i + NUM_DEVS_DSK] = 0;
        dtrack[i + (NUM_DEVS_DSK * 2)] = 077777;
        fmt_cyl[i + (NUM_DEVS_DSK * 2)] = 077777;
        arm_cyl[i + (NUM_DEVS_DSK * 2)] = 0;
        dtrack[i + (NUM_DEVS_DSK * 3)] = 077777;
        fmt_cyl[i + (NUM_DEVS_DSK * 3)] = 077777;
        arm_cyl[i + (NUM_DEVS_DSK * 3)] = 0;
        t = dptr->units[i].u4;
        /* Fill in max capacity */
        dptr->units[i + NUM_DEVS_DSK].u3 = 0xff;
        dptr->units[i + (NUM_DEVS_DSK * 2)].u3 = 0xff;
        dptr->units[i + (NUM_DEVS_DSK * 3)].u3 = 0xff;
        dptr->units[i].capac = disk_type[t].mods * disk_type[t].bpt *
            disk_type[t].arms * disk_type[t].track * disk_type[t].cyl;
        if (disk_type[t].arms > 1)
            dptr->units[i + NUM_DEVS_DSK].u3 =
                0x10 | dptr->units[i].u3 | (i << 8);
        if (disk_type[t].mods > 1) {
            dptr->units[i + (NUM_DEVS_DSK * 2)].u3 = (i<<8) | (dptr->units[i].u3 + 1);
            if (disk_type[t].arms > 1)
                dptr->units[i + (NUM_DEVS_DSK * 3)].u3 = (i<<8) | 0x10 |
                                                        (dptr->units[i].u3 + 1);
        }
    }
    return SCPE_OK;
}

/* Disk option setting commands */

t_stat
dsk_set_type(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    int                 i, u;

    if (cptr == NULL)
        return SCPE_ARG;
    if (uptr == NULL)
        return SCPE_IERR;
    if (uptr->flags & UNIT_ATT)
        return SCPE_ALATT;
    for (i = 0; disk_type[i].name != 0; i++) {
        if (strcmp(disk_type[i].name, cptr) == 0) {
            uptr->u4 = i;
            uptr[NUM_DEVS_DSK].u4 = i;
            uptr[NUM_DEVS_DSK].u3 = 0xff;
            uptr[NUM_DEVS_DSK * 2].u4 = i;
            uptr[NUM_DEVS_DSK * 2].u3 = 0xff;
            uptr[NUM_DEVS_DSK * 3].u4 = i;
            uptr[NUM_DEVS_DSK * 3].u3 = 0xff;
            uptr->capac = disk_type[i].mods * disk_type[i].bpt *
                disk_type[i].arms * disk_type[i].track * disk_type[i].cyl;
            /* Adjust other disks in case changed number arms/modules */
            u = uptr->u3 & 0xf0f;
            if (disk_type[uptr->u4].arms > 1)
                uptr[NUM_DEVS_DSK].u3 = u | 0x10;
            if (disk_type[uptr->u4].mods > 1) {
                uptr[NUM_DEVS_DSK * 2].u3 = u + 1;
                if (disk_type[uptr->u4].arms > 1)
                    uptr[NUM_DEVS_DSK * 3].u3 = (u + 1) | 0x10;
            }
            return SCPE_OK;
        }
    }
    return SCPE_ARG;
}

t_stat
dsk_get_type(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    if (uptr == NULL)
        return SCPE_IERR;
    fputs(disk_type[uptr->u4].name, st);
    return SCPE_OK;
}

t_stat
dsk_set_module(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    int                 u;

    if (cptr == NULL)
        return SCPE_ARG;
    if (uptr == NULL)
        return SCPE_IERR;
    if (uptr->flags & UNIT_ATT)
        return SCPE_ALATT;
    if (*cptr == '\0' || cptr[1] != '\0')
        return SCPE_ARG;
    if (*cptr < '0' || *cptr > '8')
        return SCPE_ARG;
    if (*cptr & 1)
        return SCPE_ARG;
    u = uptr->u3 & 0xf00;
    uptr->u3 = u | (*cptr - '0');
    uptr[NUM_DEVS_DSK].u3 = 0xff;
    uptr[NUM_DEVS_DSK * 2].u3 = 0xff;
    uptr[NUM_DEVS_DSK * 3].u3 = 0xff;
    if (disk_type[uptr->u4].arms > 1)
        uptr[NUM_DEVS_DSK].u3 = u | 0x10 | (*cptr - '0');
    if (disk_type[uptr->u4].mods > 1) {
        uptr[NUM_DEVS_DSK * 2].u3 = u | ((*cptr - '0') + 1);
        if (disk_type[uptr->u4].arms > 1)
            uptr[NUM_DEVS_DSK * 3].u3 = u | 0x10 | ((*cptr - '0') + 1);
    }
    return SCPE_OK;
}

t_stat
dsk_get_module(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    if (uptr == NULL)
        return SCPE_IERR;
    fprintf(st, "Module=%d", uptr->u3 & 0xff);
    return SCPE_OK;
}

t_stat dsk_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag,
    const char *cptr)
{
      int i;
fprintf (st, "IBM 7631 Disk File Controller\n\n");
fprintf (st, "The IBM 7631 Disk File Controller supports several types of ");
fprintf (st, "disk drives and\ndrums. The drive must be formatted for use ");
fprintf (st, "of the system. This is handled by\nutilities provided by the ");
fprintf (st, "operating system. This will write a special format\ntrack.\n\n");
fprintf (st, "Use:\n\n");
fprintf (st, "    sim> SET DKn TYPE=type\n");
fprintf (st, "Type can be: ");
for (i = 0; disk_type[i].name != 0; i++) {
    fprintf(st, "%s", disk_type[i].name);
    if (disk_type[i+1].name != 0)
        fprintf(st, ", ");
}
fprintf (st, ".\nEach drive has the following storage capacity:\n\n");
for (i = 0; disk_type[i].name != 0; i++) {
    int32 size = disk_type[i].mods * disk_type[i].bpt *
                disk_type[i].arms * disk_type[i].track * disk_type[i].cyl;
    char  sm = 'K';
    size /= 1024;
    if (size > 5000) {
        size /= 1024;
        sm = 'M';
    }
    fprintf(st, "      %-8s %4d%cB %d modules\n", disk_type[i].name, size, sm,
           disk_type[i].mods);
}
fprintf (st, "\nTo enable formating the format switch must be set ");
fprintf (st, "to enable, and the Home\nAddress 2 write must be enabled.\n");
fprintf (st, "To do this:\n\n");
fprintf (st, "     sim> SET DKn FORMAT HA2\n\n");
fprintf (st, "To prevent accidental formating of the drive use:\n\n");
fprintf (st, "     sim> SET DKn NOFORMAT NOHA2\n\n");
help_set_chan_type(st, dptr, "IBM 7631 Disk File");
fprint_set_help (st, dptr);
fprint_show_help (st, dptr);
return SCPE_OK;
}

const char *dsk_description (DEVICE *dptr)
{
return "IBM 7631 disk file controller";
}

#endif
