/* i7094_dsk.c: 7631 file control (disk/drum) simulator

   Copyright (c) 2005-2008, Robert M Supnik

   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
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Robert M Supnik shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Robert M Supnik.

   dsk          7631 file control

   The 7631 is a controller for multiple serial bit stream devices such as
   disks or drums.  It supports the

   1301         fixed disk
   1302         fixed disk
   2302         fixed disk
   7320         drum

   The 7631 supports variable record formatting, user-specified record numbering,
   and other complex features.  Each track has

        home address 1:         the track number, 4 BCD digits (implicit)
        home address 2:         user-specified track identifier, 6 BCD chars
        records 1..n:           variably formatted records, each consisting of
          record address:       user-specified record identifier, 4 BCD digits
                                 and 2 BCD characters
          record data:          36b words

   To deal with this, the simulator provides a container of 500 (7320/1301) or
   1000 (1302/2302) words per track.  Each track starts with home address 2
   and then contains a variable number of records.  Each record has a two-word
   header followed by data:

        word 0:                 record length without header
        word 1:                 record address
        word 2:                 start of data
        word 2+n-1:             end of data
        word 2+n+2:             start of next record

   A record length of 0 indicates end of valid data on the track.

   Orders to the 7631 are 10 BCD digits (60b), consisting of two words:

        word 0:                 op-op-access-module-d1-d2
        word 1:                 d3-d4-d5-d6-x-x

   Depending on the opcode, d1:d6 can be a track number plus home address 2,
   or a record number.

   Status from the 7631 is also 10 BCD digits (60b), with 36b in the first
   word, and 24b (plus 12b of zeroes) in the second.

   Because modules can have two access arms that seek independently, each
   module m is represented by two units: unit m for access 0 and unit m+10
   for access 1.  This requires tricky bookkeeping to be sure that the
   service routine is using the 'right' unit.

   Limitations of the simulation:

   - HA2 and record address must be exactly 6 characters (one word)
   - Record lengths must be exact multiples of 6 characters
   - Seek timing is fixed rather than based on seek length
*/

/* Definitions */

#include "i7094_defs.h"
#include <math.h>

#define DSK_NUMDR       10                              /* modules/controller */
#define DSK_SNS         (2 * DSK_NUMDR)                 /* dummy unit for sense */

/* Drive geometry */

#define DSK_WDSPT_7320  500                             /* words/track */
#define DSK_WDSPT_1301  500
#define DSK_WDSPT_1302  1000
#define DSK_WDSPT_2302  1000
#define DSK_TRKPC_7320  400                             /* tracks/cylinder */
#define DSK_TRKPC_1301  40
#define DSK_TRKPC_1302  40
#define DSK_TRKPC_2302  40
#define DSK_CYLPA_7320  1                               /* cylinders/access */
#define DSK_CYLPA_1301  250
#define DSK_CYLPA_1302  250
#define DSK_CYLPA_2302  250
#define DSK_TRKPA_7320  (DSK_TRKPC_7320*DSK_CYLPA_7320) /* tracks/access */
#define DSK_TRKPA_1301  (DSK_TRKPC_1301*DSK_CYLPA_1301)
#define DSK_TRKPA_1302  (DSK_TRKPC_1302*DSK_CYLPA_1302)
#define DSK_TRKPA_2302  (DSK_TRKPC_2302*DSK_CYLPA_2302)
#define DSK_ACCPM_7320  1                               /* access/module */
#define DSK_ACCPM_1301  1
#define DSK_ACCPM_1302  2
#define DSK_ACCPM_2302  2
#define DSK_FMCPT_7320  2868                            /* format chars/track */
#define DSK_FMCPT_1301  2868
#define DSK_FMCPT_1302  5942
#define DSK_FMCPT_2302  5942
#define SIZE_7320       (DSK_WDSPT_7320*DSK_TRKPA_7320*DSK_ACCPM_7320)
#define SIZE_1301       (DSK_WDSPT_1301*DSK_TRKPA_1301*DSK_ACCPM_1301)
#define SIZE_1302       (DSK_WDSPT_1302*DSK_TRKPA_1302*DSK_ACCPM_1302)
#define SIZE_2302       (DSK_WDSPT_2302*DSK_TRKPA_2302*DSK_ACCPM_2302)
#define DSK_BUFSIZ      (DSK_WDSPT_2302)
#define DSK_DA(a,t,d)   (((((a) * dsk_tab[d].trkpa) + (t)) * dsk_tab[d].wdspt) *\
                        sizeof (t_uint64))

/* Unit flags */

#define UNIT_V_INOP0    (UNIT_V_UF + 0)                 /* acc 0 inoperative */
#define UNIT_V_INOP1    (UNIT_V_UF + 1)                 /* acc 1 inoperative */
#define UNIT_V_FMTE     (UNIT_V_UF + 2)                 /* format enabled */
#define UNIT_V_TYPE     (UNIT_V_UF + 3)                 /* drive type */
#define UNIT_M_TYPE     03
#define UNIT_INOP0      (1 << UNIT_V_INOP0)
#define UNIT_INOP1      (1 << UNIT_V_INOP1)
#define UNIT_FMTE       (1 << UNIT_V_FMTE)
#define UNIT_TYPE       (UNIT_M_TYPE << UNIT_V_TYPE)
#define  TYPE_7320      (0 << UNIT_V_TYPE)
#define  TYPE_1301      (1 << UNIT_V_TYPE)
#define  TYPE_1302      (2 << UNIT_V_TYPE)
#define  TYPE_2302      (3 << UNIT_V_TYPE)
#define GET_DTYPE(x)    (((x) >> UNIT_V_TYPE) & UNIT_M_TYPE)
#define TRK             u3                              /* track */
#define SKF             u4                              /* seek in progress */

/* Track/record structure */

#define THA2            0                               /* home address 2 */
#define HA2_MASK        INT64_C(0777700000000)          /* two chars checked */
#define T1STREC         1                               /* start of records */
#define RLNT            0                               /* record length */
#define RADDR           1                               /* record address */
#define RDATA           2                               /* start of data */
#define REC_MASK        INT64_C(0171717177777)          /* 4 digits, 2 chars */

/* Command word (60b) - 10 BCD digits */

#define OP1             0                               /* opcode */
#define OP2             1
#define ACC             2                               /* access */
#define MOD             3                               /* module */
#define T1              4                               /* track */
#define T2              5
#define T3              6
#define T4              7

/* Disk states */

#define DSK_IDLE        0

/* Status word (60b) */

#define DSKS_PCHK       INT64_C(004000000000000000000)  /* prog check */
#define DSKS_DCHK       INT64_C(002000000000000000000)  /* data check */
#define DSKS_EXCC       INT64_C(001000000000000000000)  /* exc cond */
#define DSKS_INVS       INT64_C(000200000000000000000)  /* invalid seq */
#define DSKS_INVC       INT64_C(000040000000000000000)  /* invalid opcode */
#define DSKS_FMTC       INT64_C(000020000000000000000)  /* format check */
#define DSKS_NRCF       INT64_C(000010000000000000000)  /* no record found */
#define DSKS_INVA       INT64_C(000002000000000000000)  /* invalid address */
#define DSKS_RSPC       INT64_C(000000400000000000000)  /* response check */
#define DSKS_CMPC       INT64_C(000000200000000000000)  /* compare check */
#define DSKS_PARC       INT64_C(000000100000000000000)  /* parity check */
#define DSKS_ACCI       INT64_C(000000020000000000000)  /* access inoperative */
#define DSKS_ACCN       INT64_C(000000004000000000000)  /* access not ready */
#define DSKS_DSKE       INT64_C(000000002000000000000)  /* disk error */
#define DSKS_FILE       INT64_C(000000001000000000000)  /* file error */
#define DSKS_6B         INT64_C(000000000040000000000)  /* six bit mode */
#define DSKS_ATN0       INT64_C(000000000002000000000)  /* attention start */
#define DSKS_PALL       INT64_C(000777000000000000000)
#define DSKS_DALL       INT64_C(000000740000000000000)
#define DSKS_EALL       INT64_C(000000037000000000000)
#define DSKS_ALLERR     INT64_C(007777777000000000000)

/* Commands - opcode 0 */

#define DSKC_NOP        0x00
#define DSKC_RLS        0x04
#define DSKC_8B         0x08
#define DSKC_6B         0x09

/* Commands - opcode 8 */

#define DSKC_SEEK       0x0                             /* seek */
#define DSKC_SREC       0x2                             /* single record */
#define DSKC_WFMT       0x3                             /* write format */
#define DSKC_TNOA       0x4                             /* track no addr */
#define DSKC_CYL        0x5                             /* cyl no addr */
#define DSKC_WCHK       0x6                             /* write check */
#define DSKC_ACCI       0x7                             /* set acc inoperative */
#define DSKC_TWIA       0x8                             /* track with addr */
#define DSKC_THA        0x9                             /* track home addr */

/* CTSS record structure */

#define CTSS_HA2        INT64_C(0676767676767)          /* =HXXXXXX */
#define CTSS_RLNT       435                             /* data record */
#define CTSS_D1LNT      31                              /* padding */
#define CTSS_D2LNT      14
#define CTSS_D3LNT      16
#define CTSS_DLLNT      1
#define CTSS_RA1        2
#define CTSS_RA2        8

/* Data and declarations */

typedef struct {
    const char          *name;
    uint32              accpm;                          /* acc/module: 1 or 2 */
    uint32              wdspt;                          /* wds/track: 500 or 1000 */
    uint32              trkpc;                          /* trks/cyl: 1 or 40 */
    uint32              trkpa;                          /* trks/acc: 400 or 10000 */
    uint32              fchpt;                          /* format ch/track */
    uint32              size;
    } DISK_TYPE;

const DISK_TYPE dsk_tab[4] = {
    { "7320", DSK_ACCPM_7320, DSK_WDSPT_7320,
      DSK_TRKPC_7320, DSK_TRKPA_7320, DSK_FMCPT_7320, SIZE_7320 },
    { "1301", DSK_ACCPM_1301, DSK_WDSPT_1301,
      DSK_TRKPC_1301, DSK_TRKPA_1301, DSK_FMCPT_1301, SIZE_1301 },
    { "1302", DSK_ACCPM_1302, DSK_WDSPT_1302,
      DSK_TRKPC_1302, DSK_TRKPA_1302, DSK_FMCPT_1302, SIZE_1302 },
    { "2302", DSK_ACCPM_2302, DSK_WDSPT_2302,
      DSK_TRKPC_2302, DSK_TRKPA_2302, DSK_FMCPT_2302, SIZE_2302 }
    };

/* 7320/1301 format track characters */

uint8 fmt_thdr_7320[] = {
    4, 4, 4,                                            /* gap 1 */
    3, 3, 3, 3, 3, 3, 3, 3, 3,                          /* ha1 */
    4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4,                 /* gap 2 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0                     /* ha2 */
    };
uint8 fmt_rhdr_7320[] = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,                 /* x gap */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,                       /* record addr */
    2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,                 /* y gap */
    1, 1, 1, 1, 0                                       /* record ovhd */
    };

/* 1302/2302 format track characters */

uint8 fmt_thdr_1302[] = {
    4, 4, 4, 4, 4, 4,                                   /* gap 1 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,                 /* ha1 */
    4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4,        /* gap 2 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0            /* ha2 */
    };
uint8 fmt_rhdr_1302[] = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,        /* x gap */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,              /* record addr */
    2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,        /* y gap */
    1, 1, 1, 1, 1, 1, 1, 0                              /* record ovhd */
    };

/* CTSS 7320/1301 track format table */

uint32 ctss_fmt_7320[] = {
    CTSS_RLNT, CTSS_D3LNT, CTSS_DLLNT, 0
    };

/* CTSS 1302/2302 track format table */

uint32 ctss_fmt_1302[] = {
    CTSS_RLNT, CTSS_D1LNT, CTSS_D2LNT,
    CTSS_RLNT, CTSS_D3LNT, CTSS_DLLNT, 0
    };

uint32 dsk_ch = CH_C;                                   /* disk channel */
uint32 dsk_acc = 0;                                     /* access */
uint32 dsk_mod = 0;                                     /* module */
uint32 dsk_sta = 0;                                     /* disk state */
uint32 dsk_mode = 0;                                    /* I/O mode */
uint32 dsk_wchk = 0;                                    /* write check flag */
uint32 dsk_ctime = 10;                                  /* command time */
uint32 dsk_stime = 1000;                                /* seek time */
uint32 dsk_rtime = 100;                                 /* rotational latency */
uint32 dsk_wtime = 2;                                   /* word time */
uint32 dsk_gtime = 5;                                   /* gap time */
uint32 dsk_rbase = 0;                                   /* record tracking */
uint32 dsk_rptr = 0;
uint32 dsk_rlim = 0;
uint32 dsk_stop = 0;
uint32 dsk_fmt_cntr = 0;                                /* format counter */
t_uint64 dsk_rec = 0;                                   /* rec/home addr (36b) */
t_uint64 dsk_sns = 0;                                   /* sense data (60b) */
t_uint64 dsk_cmd = 0;                                   /* BCD command (60b) */
t_uint64 dsk_chob = 0;                                  /* chan output buffer */
uint32 dsk_chob_v = 0;                                  /* valid */
t_uint64 dsk_buf[DSK_BUFSIZ];                           /* transfer buffer */

extern uint32 ch_req;

t_stat dsk_svc (UNIT *uptr);
t_stat dsk_svc_sns (UNIT *uptr);
t_stat dsk_reset (DEVICE *dptr);
t_stat dsk_attach (UNIT *uptr, CONST char *cptr);
t_stat dsk_set_size (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat dsk_chsel (uint32 ch, uint32 sel, uint32 unit);
t_stat dsk_chwr (uint32 ch, t_uint64 val, uint32 flags);
t_stat dsk_new_cmd (uint32 ch, t_uint64 cmd);
t_stat dsk_uend (uint32 ch, t_uint64 stat);
t_stat dsk_recad (uint32 trk, uint32 rec, uint32 acc, uint32 mod, t_uint64 *res);
t_uint64 dsk_acc_atn (uint32 u);
t_stat dsk_init_trk (UNIT *udptr, uint32 trk);
t_stat dsk_xfer_done (UNIT *uaptr, uint32 dtyp);
t_stat dsk_wr_trk (UNIT *uptr, uint32 trk);
t_bool dsk_get_fmtc (uint32 dtyp, uint8 *fc);
t_bool dsk_qdone (uint32 ch);
t_stat dsk_show_format (FILE *st, UNIT *uptr, int32 val, CONST void *desc);

/* DSK data structures

   dsk_dev      DSK device descriptor
   dsk_unit     DSK unit descriptor
   dsk_reg      DSK register list
*/

DIB dsk_dib = { &dsk_chsel, &dsk_chwr };

UNIT dsk_unit[] = {
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             TYPE_7320, SIZE_7320) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             UNIT_DIS+TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             UNIT_DIS+TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             UNIT_DIS+TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             UNIT_DIS+TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
             UNIT_DIS+TYPE_2302, SIZE_2302) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc, UNIT_DIS, 0) },
    { UDATA (&dsk_svc_sns, UNIT_DIS, 0) }
    };

REG dsk_reg[] = {
    { ORDATA (STATE, dsk_sta, 6) },
    { ORDATA (ACCESS, dsk_acc, 1) },
    { ORDATA (MODULE, dsk_mod, 4) },
    { ORDATA (RECORD, dsk_rec, 36) },
    { ORDATA (MODE, dsk_mode, 4) },
    { ORDATA (SENSE, dsk_sns, 60) },
    { ORDATA (BCDCMD, dsk_cmd, 60) },
    { ORDATA (CHOB, dsk_chob, 36) },
    { FLDATA (CHOBV, dsk_chob_v, 0) },
    { FLDATA (STOP, dsk_stop, 0) },
    { DRDATA (FCNTR, dsk_fmt_cntr, 13) },
    { BRDATA (BUF, dsk_buf, 8, 36, DSK_BUFSIZ) },
    { DRDATA (RBASE, dsk_rbase, 10), REG_RO },
    { DRDATA (RPTR, dsk_rptr, 10), REG_RO },
    { DRDATA (RLIM, dsk_rlim, 10), REG_RO },
    { DRDATA (CHAN, dsk_ch, 3), REG_HRO },
    { DRDATA (STIME, dsk_stime, 24), REG_NZ + PV_LEFT },
    { DRDATA (RTIME, dsk_rtime, 24), REG_NZ + PV_LEFT },
    { DRDATA (WTIME, dsk_wtime, 24), REG_NZ + PV_LEFT },
    { DRDATA (GTIME, dsk_gtime, 24), REG_NZ + PV_LEFT },
    { DRDATA (CTIME, dsk_ctime, 24), REG_NZ + PV_LEFT },
    { URDATA (TRACK, dsk_unit[0].TRK, 10, 14, 0,
              2 * DSK_NUMDR, PV_LEFT) },
    { URDATA (SEEKF, dsk_unit[0].SKF, 10, 1, 0,
              2 * DSK_NUMDR, PV_LEFT | REG_HRO) },
    { URDATA (CAPAC, dsk_unit[0].capac, 10, T_ADDR_W, 0,
              DSK_NUMDR, PV_LEFT | REG_HRO) },
    { NULL }
    };

MTAB dsk_mtab[] = {
    { UNIT_INOP0 + UNIT_INOP1, 0, "operational", "OPERATIONAL" },
    { UNIT_INOP0 + UNIT_INOP1, UNIT_INOP0, "access 0 inoperative", NULL },
    { UNIT_INOP0 + UNIT_INOP1, UNIT_INOP1, "access 1 inoperative", NULL },
    { UNIT_FMTE, UNIT_FMTE, "formating enabled", "FORMAT" },
    { UNIT_FMTE, 0, "formating disabled", "NOFORMAT" },
    { UNIT_TYPE, TYPE_7320, "7320", "7320", &dsk_set_size },
    { UNIT_TYPE, TYPE_1301, "1301", "1301", &dsk_set_size },
    { UNIT_TYPE, TYPE_1302, "1302", "1302", &dsk_set_size },
    { UNIT_TYPE, TYPE_2302, "2302", "2302", &dsk_set_size },
    { MTAB_XTD|MTAB_VDV, 0, "CHANNEL", NULL,
      NULL, &ch_show_chan, NULL },
    { MTAB_XTD|MTAB_VUN|MTAB_NMO, 1, "FORMAT", NULL,
      NULL, &dsk_show_format, NULL },
    { 0 }
    };

DEVICE dsk_dev = {
    "DSK", dsk_unit, dsk_reg, dsk_mtab,
    (DSK_NUMDR * 2) + 1, 10, 24, 1, 8, 36,
    NULL, NULL, &dsk_reset,
    NULL, &dsk_attach, NULL,
    &dsk_dib, DEV_DIS
    };

/* Disk channel select, from 7909 channel program */

t_stat dsk_chsel (uint32 ch, uint32 sel, uint32 unit)
{
uint32 u;

dsk_ch = ch;
if (dsk_sta != DSK_IDLE)                                /* not idle? seq check */
    dsk_uend (ch, DSKS_INVS);

switch (sel) {

    case CHSL_CTL:                                      /* control */
        ch_req |= REQ_CH (ch);                          /* request channel */
        break;

    case CHSL_SNS:                                      /* sense */
        if (sim_is_active (&dsk_unit[DSK_SNS]))         /* already sensing? */
            return dsk_uend (ch, DSKS_INVS);            /* sequence error */
        sim_activate (&dsk_unit[DSK_SNS], dsk_ctime);   /* set timer */
        dsk_stop = 0;
        break;

    case CHSL_RDS:                                      /* read */
        if (dsk_mode == DSKC_WFMT)                      /* write format? */
            return dsk_uend (ch, DSKS_INVS);            /* sequence error */
    case CHSL_WRS:                                      /* write */
        if (dsk_mode == 0)                              /* no mode? seq check */
            dsk_uend (ch, DSKS_INVS);
        if (dsk_mode == DSKC_WFMT)                      /* format? fake sel */
            sel = CHSL_FMT;
        u = (dsk_acc * DSK_NUMDR) + dsk_mod;            /* access unit number */
        if (sim_is_active (&dsk_unit[u]))               /* access in use? */
            return dsk_uend (ch, DSKS_ACCN);            /* access not ready */
        sim_activate (&dsk_unit[u], dsk_rtime);         /* rotational time */
        break;

    default:                                            /* other */
        return STOP_ILLIOP;
        }

dsk_sta = sel;                                          /* set new state */
return SCPE_OK;
}

/* Disk channel write, from 7909 channel program */

t_stat dsk_chwr (uint32 ch, t_uint64 val, uint32 stopf)
{
if (stopf)                                              /* stop? */
    dsk_stop = 1;

else {
    val = val & DMASK;
    switch (dsk_sta) {                                 /* case on state */

    case CHSL_CTL:                                      /* control */
        dsk_cmd = val << 24;
        if (val & INT64_C(0100000000000)) {             /* need 2nd word? */
            ch_req |= REQ_CH (ch);                      /* req ch for 2nd */
            dsk_sta = CHSL_CTL|CHSL_2ND;                /* next state */
            return SCPE_OK;
            }
        return dsk_new_cmd (ch, dsk_cmd);               /* no, do cmd */

    case CHSL_CTL|CHSL_2ND:                             /* 2nd control */
        dsk_cmd |= (val >> 12);
        return dsk_new_cmd (ch, dsk_cmd);               /* do cmd */

    default:
        dsk_chob = val;                                 /* store data */
        dsk_chob_v = 1;                                 /* set valid */
        }
    }

return SCPE_OK;
}

/* New command - end of CTL sequence */

t_stat dsk_new_cmd (uint32 ch, t_uint64 cmd)
{
uint32 i, d, a, m, u, trk, dtyp, bcd[8];

ch_req |= REQ_CH (ch);                                  /* req ch for end */
ch9_set_end (ch, 0);                                    /* set end flag */
dsk_sta = DSK_IDLE;                                     /* ctrl is idle */

for (i = 0; i < 8; i++) {                               /* get chars from cmd */
    d = (uint32) (cmd >> (6 * (9 - i))) & BCD_MASK;
    if (d == BCD_ZERO)
        d = 0;
    else if (d == 0)                                    /* BCD zero cvt */
        d = BCD_ZERO;
    bcd[i] = d;
    }

if (bcd[OP1] == 0) {                                    /* cmd = 0x? */

    switch (bcd[OP2]) {                                 /* case on x */

    case DSKC_NOP:                                      /* nop */
    case DSKC_RLS:                                      /* release */
        break;

    case DSKC_8B:                                       /* 8b mode */
        dsk_sns &= ~DSKS_6B;
        break;

    case DSKC_6B:                                       /* 6b mode */
        dsk_sns |= DSKS_6B;
        break;

    default:                                            /* unknown */
        return dsk_uend (ch, DSKS_INVC);                /* invalid opcode */
        }                                               /* end case op2 */
    return SCPE_OK;
    }                                                   /* end if */

else if (bcd[OP1] == 8) {                               /* cmd = 8x? */

    a = bcd[ACC];                                       /* get access, */
    m = bcd[MOD];                                       /* module */
    u = (a * DSK_NUMDR) + m;                            /* unit for access */
    if ((m > DSK_NUMDR) ||                              /* invalid module? */
        (dsk_unit[m].flags & UNIT_DIS))                 /* disabled module? */
        return dsk_uend (ch, DSKS_ACCI);
    dtyp = GET_DTYPE (dsk_unit[m].flags);               /* get drive type */
    if ((a >= dsk_tab[dtyp].accpm) ||                   /* invalid access? */
        (dsk_unit[m].flags & (UNIT_INOP0 << a)))        /* access inop? */
        return dsk_uend (ch, DSKS_ACCI);
    if ((bcd[T1] > 9) || (bcd[T2] > 9) || (bcd[T3] > 9) || (bcd[T4] > 9))
        trk = dsk_tab[dtyp].trkpa + 1;                  /* bad track */
    else trk = (((((bcd[T1] * 10) + bcd[T2]) * 10) + bcd[T3]) * 10) + bcd[T4];

    if (bcd[OP2] == DSKC_WCHK) {                        /* write check */
        if (dsk_mode == 0)                              /* no prior operation? */
            return dsk_uend (ch, DSKS_INVS);
        bcd[OP2] = dsk_mode;                            /* use prior mode */
        dsk_wchk = 1;                                   /* set write check */
        }
    else dsk_wchk = 0;
    dsk_sns &= ~(DSKS_ALLERR | dsk_acc_atn (u));        /* clear err, atn */
    dsk_stop = 0;                                       /* clear stop */

    switch (bcd[OP2]) {

    case DSKC_SEEK:                                     /* seek */
        if ((trk >= dsk_tab[dtyp].trkpa) &&             /* inv track? */
            ((dtyp == TYPE_7320) ||                     /* drum or not CE? */
             (bcd[T1] > 9) || (bcd[T2] != BCD_AT) ||
             (bcd[T3] > 9) || (bcd[T4] > 9)))
            return dsk_uend (ch, DSKS_INVA);
        if (sim_is_active (&dsk_unit[u]))               /* selected acc busy? */
            return dsk_uend (ch, DSKS_ACCN);
        dsk_unit[u].SKF = 1;                            /* set seeking flag */
        dsk_unit[u].TRK = trk;                          /* sel acc on cyl */
        sim_activate (&dsk_unit[u], dsk_stime);         /* seek */
        dsk_mode = 0;                                   /* clear I/O mode */
        return SCPE_OK;

    case DSKC_ACCI:                                     /* access inoperative */
        dsk_unit[m].flags |= (UNIT_INOP0 << a);         /* set correct flag */
        dsk_mode = 0;                                   /* clear I/O mode */
        return SCPE_OK;

    case DSKC_SREC:                                     /* single record */
        break;                                          /* no verification */

    case DSKC_WFMT:                                     /* format */
        if (!(dsk_unit[m].flags & UNIT_FMTE))           /* format enabled? */
            return dsk_uend (ch, DSKS_FMTC);            /* no, error */
    case DSKC_TNOA:                                     /* track no addr */
    case DSKC_CYL:                                      /* cyl no addr */
    case DSKC_TWIA:                                     /* track with addr */
    case DSKC_THA:                                      /* track home addr */
        if (trk != (uint32) dsk_unit[u].TRK)            /* on track? */
            return dsk_uend (ch, DSKS_NRCF);
        break;    

    default:
        return dsk_uend (ch, DSKS_INVC);                /* invalid opcode */
        }

    dsk_acc = a;                                        /* save access */
    dsk_mod = m;                                        /* save module */
    dsk_rec = cmd & DMASK;                              /* save rec/home addr */
    dsk_mode = bcd[OP2];                                /* save mode */
    return SCPE_OK;
    }

return dsk_uend (ch, DSKS_INVC);                        /* invalid opcode */
}

/* Sense unit service */

t_stat dsk_svc_sns (UNIT *uptr)
{
t_uint64 dat;

switch (dsk_sta) {                                      /* case on state */

    case CHSL_SNS:                                      /* prepare data */
        dsk_buf[0] = (dsk_sns >> 24) & DMASK;           /* buffer is 2 words */
        dsk_buf[1] = (dsk_sns << 12) & DMASK;
        dsk_rptr = 0;
        dsk_rlim = 2;
        dsk_sta = CHSL_SNS|CHSL_2ND;                    /* 2nd state */
        break;

    case CHSL_SNS|CHSL_2ND:                             /* second state */
        if (dsk_rptr >= dsk_rlim) {                     /* end of buffer? */
            ch9_set_end (dsk_ch, 0);                    /* set end */
            ch_req |= REQ_CH (dsk_ch);                  /* request channel */
            dsk_sta = CHSL_SNS|CHSL_3RD;                /* 3rd state */
            sim_activate (uptr, dsk_ctime);             /* longer wait */
            return SCPE_OK;
            }
        dat = dsk_buf[dsk_rptr++];                      /* get word */
        if (!dsk_stop)                                  /* send wd to chan */
            ch9_req_rd (dsk_ch, dat);
        break;

    case CHSL_SNS|CHSL_3RD:                             /* 3rd state */
        if (dsk_qdone (dsk_ch))                         /* done? exit */
            return SCPE_OK;
        dsk_sta = CHSL_SNS;                             /* repeat sequence */
        break;
        }

sim_activate (uptr, dsk_wtime);                         /* sched next */
return SCPE_OK;
}

/* Seek, read, write unit service */

t_stat dsk_svc (UNIT *uaptr)
{
uint32 i, dtyp, trk;
uint8 fc, *format;
t_uint64 rdat;
UNIT *udptr;
t_stat r;

if (uaptr->SKF) {                                       /* seeking? */
    uint32 u = uaptr - dsk_dev.units;                   /* get unit */
    uaptr->SKF = 0;                                     /* seek done */
    dsk_sns |= dsk_acc_atn (u);                         /* set atn bit */
    ch9_set_atn (dsk_ch);                               /* set atn flag */
    return SCPE_OK;
    }

udptr = dsk_dev.units + dsk_mod;                        /* data unit */
if (udptr->flags & (UNIT_INOP0 << dsk_acc))             /* acc inoperative? */
    return dsk_uend (dsk_ch, DSKS_ACCI);                /* error */
if ((udptr->flags & UNIT_ATT) == 0) {                   /* not attached? */
    dsk_uend (dsk_ch, DSKS_ACCI);                       /* error */
    return SCPE_UNATT;
    }

dtyp = GET_DTYPE (udptr->flags);                        /* get data drive type */
trk = uaptr->TRK;                                       /* get access track */

switch (dsk_sta) {                                      /* case on state */

    case CHSL_RDS:                                      /* read start */
        if ((r = dsk_init_trk (udptr, trk))) {          /* read track, err? */
            return ((r == ERR_NRCF)? SCPE_OK: r);       /* rec not fnd ok */
            }
        dsk_sta = CHSL_RDS|CHSL_2ND;                    /* next state */
        break;

    case CHSL_RDS|CHSL_2ND:                             /* read data transmit */
        if ((r = dsk_xfer_done (uaptr, dtyp))) {        /* transfer done? */
            if (r != ERR_ENDRC)                         /* error? */
                return r;
            dsk_sta = CHSL_RDS|CHSL_3RD;                /* next state */
            sim_activate (uaptr, dsk_gtime);            /* gap time */
            return SCPE_OK;
            }
        rdat = dsk_buf[dsk_rptr++];                     /* get word */
        if (dsk_rptr == T1STREC)                        /* if THA, skip after HA */
            dsk_rptr++;
        if (!dsk_stop)                                  /* give to channel */
            ch9_req_rd (dsk_ch, rdat);
        break;

    case CHSL_RDS|CHSL_3RD:                             /* read end rec/trk */
        if (dsk_qdone (dsk_ch))                         /* done? exit */
            return SCPE_OK;
        dsk_sta = CHSL_RDS;                             /* repeat sequence */
        break;

    case CHSL_WRS:                                      /* write start */
        if ((r = dsk_init_trk (udptr, trk))) {          /* read track, err? */
            return ((r == ERR_NRCF)? SCPE_OK: r);       /* rec not fnd ok */
            }
        ch_req |= REQ_CH (dsk_ch);                      /* first request */
        dsk_sta = CHSL_WRS|CHSL_2ND;                    /* next state */
        dsk_chob = 0;                                   /* clr, inval buffer */
        dsk_chob_v = 0;
        break;

    case CHSL_WRS|CHSL_2ND:                             /* write data transmit */
        if (dsk_chob_v)                                 /* valid? clear */
            dsk_chob_v = 0;
        else if (!dsk_stop)                             /* no, no stop? io chk */
            ch9_set_ioc (dsk_ch);
        if (dsk_wchk) {                                 /* write check? */
            if (dsk_buf[dsk_rptr++] != dsk_chob)        /* data mismatch? */
                return dsk_uend (dsk_ch, DSKS_CMPC);    /* error */
            }
        else dsk_buf[dsk_rptr++] = dsk_chob;            /* write, store word */
        if (dsk_rptr == T1STREC)                        /* if THA, skip after HA */
            dsk_rptr++;
        if ((r = dsk_xfer_done (uaptr, dtyp))) {        /* transfer done? */
            if (r != ERR_ENDRC)                         /* error? */
                return r;
            dsk_sta = CHSL_WRS|CHSL_3RD;                /* next state */
            sim_activate (uaptr, dsk_gtime);            /* gap time */
            return SCPE_OK;
            }
        if (!dsk_stop)                                  /* more to do */
            ch_req |= REQ_CH (dsk_ch);
        break;

    case CHSL_WRS|CHSL_3RD:                             /* write done */
        if (!dsk_wchk) {                                /* if write */
            if ((r = dsk_wr_trk (udptr, trk)))          /* write track; err? */
                return r;
            }
        if (dsk_qdone (dsk_ch))                         /* done? exit */
            return SCPE_OK;
        dsk_sta = CHSL_WRS;                             /* repeat sequence */
        break;

/* Formatting takes place in five stages

   1. Clear the track buffer, request the first word from the channel
   2. Match characters against the fixed overhead (HA1, HA2, and gaps)
   3. Match characters against the per-record overhead (RA and gaps)
   4. Count the characters defining the record length
   5. See if the next character is end or gap; if gap, return to stage 3

   This formating routine is not exact.  It checks whether the format
   will fit in the container, not whether the format would fit on a
   real 7320, 1301, 1302, or 2302. */

    case CHSL_FMT:                                      /* initialization */
        for (i = 0; i < DSK_BUFSIZ; i++)                /* clear track buf */
            dsk_buf[i] = 0;
        dsk_rbase = T1STREC;                            /* init record ptr */
        dsk_rptr = 0;                                   /* init format ptr */
        dsk_fmt_cntr = 0;                               /* init counter */
        ch_req |= REQ_CH (dsk_ch);                      /* request channel */
        dsk_sta = CHSL_FMT|CHSL_2ND;                    /* next state */
        dsk_chob = 0;                                   /* clr, inval buffer */
        dsk_chob_v = 0;
        break;

    case CHSL_FMT|CHSL_2ND:                             /* match track header */
        if ((dtyp == TYPE_7320) || (dtyp == TYPE_1301))
            format = fmt_thdr_7320;
        else format = fmt_thdr_1302;
        if (!dsk_get_fmtc (dtyp, &fc))                  /* get fmt char; err? */
            return SCPE_OK;
        if (fc != format[dsk_rptr++])                   /* mismatch? */
            return dsk_uend (dsk_ch, DSKS_FMTC);        /* format check */
        if (format[dsk_rptr] == 0) {                    /* end format? */
            dsk_sta = CHSL_FMT|CHSL_3RD;                /* next state */
            dsk_rptr = 0;                               /* reset format ptr */
            }
        break;

    case CHSL_FMT|CHSL_3RD:                             /* match record header */
        if ((dtyp == TYPE_7320) || (dtyp == TYPE_1301))
            format = fmt_rhdr_7320;
        else format = fmt_rhdr_1302;
        if (!dsk_get_fmtc (dtyp, &fc))                  /* get fmt char; err? */
            return SCPE_OK;
        if (fc != format[dsk_rptr++])                   /* mismatch? */
            return dsk_uend (dsk_ch, DSKS_FMTC);        /* format check */
        if (format[dsk_rptr] == 0) {                    /* end format? */
            dsk_sta = CHSL_FMT|CHSL_4TH;                /* next state */
            dsk_rlim = 0;                               /* reset record ctr */
            }
        break;

    case CHSL_FMT|CHSL_4TH:                             /* count record size */
        if (!dsk_get_fmtc (dtyp, &fc))                  /* get fmt char; err? */
            return SCPE_OK;
        if (fc == BCD_ONE)                              /* more record? */
            dsk_rlim++;
        else {
            uint32 rsiz = dsk_rlim / 6;                 /* rec size words */
            if ((fc != BCD_TWO) ||                      /* improper end? */
                (rsiz == 0) ||                          /* smaller than min? */
                ((dsk_rlim % 6) != 0) ||                /* not multiple of 6? */
                ((dsk_rbase + rsiz + RDATA) >= dsk_tab[dtyp].wdspt))
                return dsk_uend (dsk_ch, DSKS_FMTC);    /* format check */
            dsk_buf[dsk_rbase + RLNT] = rsiz;           /* record rec lnt */
            dsk_rbase = dsk_rbase + rsiz + RDATA;       /* new rec start */
            dsk_sta = CHSL_FMT|CHSL_5TH;                /* next state */
            }
        break;

    case CHSL_FMT|CHSL_5TH:                             /* record or track end */
        if (!dsk_get_fmtc (dtyp, &fc))                  /* get fmt char; err? */
            return SCPE_OK;
        if (fc == BCD_TWO) {                            /* back to record header? */
            dsk_rptr = 2;                               /* already done 2 chars */
            dsk_sta = CHSL_FMT|CHSL_3RD;                /* record header state */
            }
        else if (fc != BCD_ONE)                         /* format check */
            dsk_uend (dsk_ch, DSKS_FMTC);
        else {
            if (!dsk_wchk) {                            /* actual write? */
                trk = trk - (trk % dsk_tab[dtyp].trkpc);        /* cyl start */
                for (i = 0; i < dsk_tab[dtyp].trkpc; i++) {     /* do all tracks */
                    if ((r = dsk_wr_trk (udptr,  trk + i)))     /* wr track; err? */
                        return r;
                    }
                }
            ch9_set_end (dsk_ch, 0);                    /* set end */
            ch_req |= REQ_CH (dsk_ch);                  /* request channel */
            dsk_sta = DSK_IDLE;                         /* disk is idle */
            return SCPE_OK;                             /* done */
            }
        break;

    default:
        return SCPE_IERR;
        }

sim_activate (uaptr, dsk_wtime);
return SCPE_OK;
}

/* Initialize data transfer

   Inputs:
        udptr   =       pointer to data unit
        trk     =       track to read
   Outputs:
        dsk_buf contains track specified by trk
        dsk_rbase, dsk_rptr, dsk_rlim are initialized
   Errors:
        SCPE_IOERR =    I/O error (fatal, uend)
        ERR_NRCF =      no record found (HA2 or record number mismatch, uend)
        STOP_INVFMT =   invalid format (fatal, uend)
*/

t_stat dsk_init_trk (UNIT *udptr, uint32 trk)
{
uint32 k, da, dtyp, rlnt;

dtyp = GET_DTYPE (udptr->flags);                        /* get drive type */
da = DSK_DA (dsk_acc, trk, dtyp);                       /* get disk address */
sim_fseek (udptr->fileref, da, SEEK_SET);               /* read track */
k = sim_fread (dsk_buf, sizeof (t_uint64), dsk_tab[dtyp].wdspt, udptr->fileref);
if (ferror (udptr->fileref)) {                          /* error? */
    sim_perror ("DSK I/O error");
    clearerr (udptr->fileref);
    dsk_uend (dsk_ch, DSKS_DSKE);
    return SCPE_IOERR;
    }
for ( ; k < dsk_tab[dtyp].wdspt; k++)                   /* zero fill */
    dsk_buf[k] = 0;
dsk_rbase = T1STREC;                                    /* record base */
rlnt = (uint32) dsk_buf[dsk_rbase + RLNT];              /* length */
dsk_rlim = dsk_rbase + rlnt + RDATA;                    /* end */
if ((rlnt == 0) || (dsk_rlim >= dsk_tab[dtyp].wdspt)) { /* invalid record? */
    dsk_uend (dsk_ch, DSKS_FMTC);
    return STOP_INVFMT;
    }
if (dsk_mode != DSKC_SREC) {                            /* not single record? */
    if (dsk_mode == DSKC_THA)                           /* trk home addr? */
        dsk_rptr = 0;
    else {
        if (((dsk_rec << 24) ^ dsk_buf[THA2]) & HA2_MASK) {
            dsk_uend (dsk_ch, DSKS_NRCF);               /* invalid HA2 */
            return ERR_NRCF;
            }
        if (dsk_mode == DSKC_TWIA)                      /* track with addr? */
            dsk_rptr = dsk_rbase + RADDR;               /* start at addr */
        else dsk_rptr = dsk_rbase + RDATA;              /* else, at data */
        }
    return SCPE_OK;
    }
while (rlnt != 0) {                                     /* until end track */
    dsk_rptr = dsk_rbase + RDATA;
    if (((dsk_rec ^ dsk_buf[dsk_rbase + RADDR]) & REC_MASK) == 0)
        return SCPE_OK;                                 /* rec found? done */
    dsk_rbase = dsk_rlim;                               /* next record */
    rlnt = (uint32) dsk_buf[dsk_rbase + RLNT];          /* length */
    dsk_rlim = dsk_rbase + rlnt + RDATA;                /* limit */
    if (dsk_rlim >= dsk_tab[dtyp].wdspt) {              /* invalid format? */
        dsk_uend (dsk_ch, DSKS_FMTC);
        return STOP_INVFMT;
        }
    }
dsk_uend (dsk_ch, DSKS_NRCF);                           /* not found */
return ERR_NRCF;
}

/* Check end of transfer

   Inputs:
        uptr    =       pointer to access unit
        dtyp    =       drive type
   Outputs:
        ERR_ENDRC =     end of record/track/cylinder, end sent, ch req if required
        SCPE_OK =       more to do, dsk_rbase, dsk_rptr, dsk_rlim may be updated
        STOP_INVFMT =   invalid format (fatal, uend sent)
*/

t_stat dsk_xfer_done (UNIT *uaptr, uint32 dtyp)
{
uint32 rlnt;

if (dsk_rptr < dsk_rlim)                                /* record done? */
    return SCPE_OK;
if (dsk_stop || !ch9_qconn (dsk_ch) ||                  /* stop or err disc or */
    (dsk_mode == DSKC_SREC)) {                          /* single record? */
    ch9_set_end (dsk_ch, 0);                            /* set end */
    ch_req |= REQ_CH (dsk_ch);                          /* request channel */
    return ERR_ENDRC;
    }
dsk_rbase = dsk_rlim;                                   /* next record */
rlnt = (uint32) dsk_buf[dsk_rbase + RLNT];              /* length */
dsk_rlim = dsk_rbase + rlnt + RDATA;                    /* end */
if ((dsk_rbase >= dsk_tab[dtyp].wdspt) ||               /* invalid format? */
    (dsk_rlim >= dsk_tab[dtyp].wdspt)) {
    dsk_uend (dsk_ch, DSKS_FMTC);
    return STOP_INVFMT;
    }
if (rlnt) {                                             /* more on track? */
    if ((dsk_mode == DSKC_THA) || (dsk_mode == DSKC_TWIA))
        dsk_rptr = dsk_rbase + RADDR;                   /* start with addr */
    else dsk_rptr = dsk_rbase + RDATA;                  /* or data */
    return SCPE_OK;
    }
if (dsk_mode == DSKC_CYL) {                             /* cylinder mode? */
    uaptr->TRK = (uaptr->TRK + 1) % dsk_tab[dtyp].trkpa;        /* incr track */
    if (uaptr->TRK % dsk_tab[dtyp].trkpc)               /* not cyl end? */
        return ERR_ENDRC;                               /* don't set end */
    }
ch9_set_end (dsk_ch, 0);                                /* set end */
ch_req |= REQ_CH (dsk_ch);                              /* request channel */
return ERR_ENDRC;
}

/* Write track back to file */

t_stat dsk_wr_trk (UNIT *udptr, uint32 trk)
{
uint32 dtyp = GET_DTYPE (udptr->flags);
uint32 da = DSK_DA (dsk_acc, trk, dtyp);

sim_fseek (udptr->fileref, da, SEEK_SET);
sim_fwrite (dsk_buf, sizeof (t_uint64), dsk_tab[dtyp].wdspt, udptr->fileref);
if (ferror (udptr->fileref)) {
    sim_perror ("DSK I/O error");
    clearerr (udptr->fileref);
    dsk_uend (dsk_ch, DSKS_DSKE);
    return SCPE_IOERR;
    }
return SCPE_OK;
}

/* Synthesize right attention bit from (access * 10 + module) */

t_uint64 dsk_acc_atn (uint32 u)
{
uint32 g, b;

g = u / 4;                                              /* bit group */
b = u % 4;                                              /* bit within group */
return (DSKS_ATN0 >> ((g * 6) + (b? b + 1: 0)));
}

/* Get next format character */

t_bool dsk_get_fmtc (uint32 dtyp, uint8 *fc)
{
uint32 cc = dsk_fmt_cntr % 6;

if (cc == 0) {                                          /* start of word? */
    if (dsk_chob_v)                                     /* valid? clear */
        dsk_chob_v = 0;
    else if (!dsk_stop)                                 /* no, no stop? io chk */
        ch9_set_ioc (dsk_ch);
    }
*fc = ((uint8) (dsk_chob >> ((5 - cc) * 6))) & 077;     /* get character */
if ((cc == 5) && !dsk_stop)                             /* end of word? */
    ch_req |= REQ_CH (dsk_ch);
if (dsk_fmt_cntr++ >= dsk_tab[dtyp].fchpt) {            /* track overflow? */
    dsk_uend (dsk_ch, DSKS_FMTC);                       /* format check */
    return FALSE;
    }
return TRUE;
}

/* Unusual end (set status and stop) */

t_stat dsk_uend (uint32 ch, t_uint64 stat)
{
dsk_sns |= stat;
dsk_sns &= ~(DSKS_PCHK|DSKS_DCHK|DSKS_EXCC);
if (dsk_sns & DSKS_PALL)
    dsk_sns |= DSKS_PCHK;
if (dsk_sns & DSKS_DALL)
    dsk_sns |= DSKS_DCHK;
if (dsk_sns & DSKS_EALL)
    dsk_sns |= DSKS_EXCC;
ch9_set_end (ch, CHINT_UEND);
ch_req |= REQ_CH (ch);
dsk_sta = DSK_IDLE;
return SCPE_OK;
}

/* Test for done */

t_bool dsk_qdone (uint32 ch)
{
if (dsk_stop || !ch9_qconn (ch)) {                      /* stop or err disc? */
    dsk_sta = DSK_IDLE;                                 /* disk is idle */
    return TRUE;
    }
return FALSE;
}

/* Reset */

t_stat dsk_reset (DEVICE *dptr)
{
uint32 i;
UNIT *uptr;

dsk_acc = 0;
dsk_mod = 0;
dsk_rec = 0;
dsk_mode = 0;
dsk_wchk = 0;
dsk_sns = 0;
dsk_cmd = 0;
dsk_sta = DSK_IDLE;
dsk_rbase = 0;
dsk_rptr = 0;
dsk_rlim = 0;
dsk_stop = 0;
dsk_fmt_cntr = 0;
dsk_chob = 0;
dsk_chob_v = 0;
for (i = 0; i < DSK_BUFSIZ; i++)
    dsk_buf[i] = 0;
for (i = 0; i <= (2 * DSK_NUMDR); i++) {
    uptr = dsk_dev.units + i;
    sim_cancel (uptr);
    uptr->TRK = 0;
    uptr->SKF = 0;
    }
return SCPE_OK;
}

/* Attach routine, test formating */

t_stat dsk_attach (UNIT *uptr, CONST char *cptr)
{
uint32 dtyp = GET_DTYPE (uptr->flags);
t_stat r;

uptr->capac = dsk_tab[dtyp].size;
r = attach_unit (uptr, cptr);
if (r != SCPE_OK)
    return r;
uptr->TRK = 0;
uptr->SKF = 0;
uptr->flags &= ~(UNIT_INOP0|UNIT_INOP1);
return dsk_show_format (stdout, uptr, 0, NULL);
}

/* Set disk size */

t_stat dsk_set_size (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint32 dtyp = GET_DTYPE (val);
uint32 u = uptr - dsk_dev.units;
UNIT *u1;

if (u & 1)
    return SCPE_ARG;
u1 = dsk_dev.units + u + 1;
if ((uptr->flags & UNIT_ATT) || (u1->flags & UNIT_ATT))
    return SCPE_ALATT;
if (val == TYPE_7320)
    u1->flags = (u1->flags & ~UNIT_DISABLE) | UNIT_DIS;
else {
    u1->flags = (u1->flags & ~UNIT_TYPE) | val | UNIT_DISABLE;
    u1->capac = dsk_tab[dtyp].size;
    }
uptr->capac = dsk_tab[dtyp].size;
return SCPE_OK;
}

/* Show format */

t_stat dsk_show_format (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
uint32 a, t, k, u, tlim, dtyp, da;
uint32 rptr, rlnt, rlim, rec, ctptr, *format;
uint32 minrsz = DSK_BUFSIZ;
uint32 maxrsz = 0;
uint32 minrno = DSK_BUFSIZ;
uint32 maxrno = 0;
t_bool ctss;
t_uint64 dbuf[DSK_BUFSIZ];
DEVICE *dptr;

if (uptr == NULL)
    return SCPE_IERR;
if ((uptr->flags & UNIT_ATT) == 0)
    return SCPE_UNATT;
dptr = find_dev_from_unit (uptr);
if (dptr == NULL)
    return SCPE_IERR;
u = uptr - dptr->units;

dtyp = GET_DTYPE (uptr->flags);
if ((dtyp == TYPE_7320) || (dtyp == TYPE_1301))
    format = ctss_fmt_7320;
else format = ctss_fmt_1302;
for (a = 0, ctss = TRUE; a < dsk_tab[dtyp].accpm; a++) {
    if (val)
        tlim = dsk_tab[dtyp].trkpa;
    else tlim = 1;
    for (t = 0; t < tlim; t++) {
        da = DSK_DA (a, t, dtyp);                       /* get disk address */
        sim_fseek (uptr->fileref, da, SEEK_SET);        /* read track */
        k = sim_fread (dbuf, sizeof (t_uint64), dsk_tab[dtyp].wdspt, uptr->fileref);
        if (ferror (uptr->fileref))                     /* error? */
            return SCPE_IOERR;
        for ( ; k < dsk_tab[dtyp].wdspt; k++)
            dbuf[k] = 0;
        rptr = T1STREC;
        rlnt = (uint32) dbuf[rptr + RLNT];
        if (dbuf[THA2] != CTSS_HA2)
            ctss = FALSE;
        if (rlnt == 0) {
            if (a || t)
                fprintf (st, "Unformatted track, unit = %d, access = %d, track = %d\n", u, a, t);
            else fprintf (st, "Unit %d is unformatted\n", u);
            return SCPE_OK;
            }
        for (rec = 0, ctptr = 0; rlnt != 0; rec++) {
            if ((format[ctptr] == 0) || format[ctptr++] != rlnt)
                ctss = FALSE;
            rlim = rptr + rlnt + RDATA;
            if (rlim >= dsk_tab[dtyp].wdspt) {
                fprintf (st, "Invalid record length %d, unit = %d, access = %d, track = %d, record = %d\n",
                    rlnt, u, a, t, rec);
                return SCPE_OK;
                }
            if (rlnt > maxrsz)
                maxrsz = rlnt;
            if (rlnt < minrsz)
                minrsz = rlnt;
            rptr = rlim;
            rlnt = (uint32) dbuf[rptr + RLNT];
            }
        if (format[ctptr] != 0)
            ctss = FALSE;
        if (rec > maxrno)
            maxrno = rec;
        if (rec < minrno)
            minrno = rec;
        }
    }
if (val == 0)
    return SCPE_OK;
if (ctss)
    fprintf (st, "CTSS format\n");
else if ((minrno == maxrno) && (minrsz == maxrsz))
    fprintf (st, "Valid fixed format, records/track = %d, record size = %d\n",
             minrno, minrsz);
else if (minrsz == maxrsz)
    fprintf (st, "Valid variable format, records/track = %d-%d, record size = %d\n",
             minrno, maxrno, minrsz);
else if (minrno == maxrno)
    fprintf (st, "Valid variable format, records/track = %d, record sizes = %d-%d\n",
             minrno, minrsz, maxrsz);
else fprintf (st, "Valid variable format, records/track = %d-%d, record sizes = %d-%d\n",
              minrno, maxrno, minrsz, maxrsz);
return SCPE_OK;
}
