/* ka10_ai.c: Systems Concepts DC-10 disk control

   Copyright (c) 2019, Lars Brinkhoff

   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
   LARS BRINKHOFF 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.

   This disk controller was probably only ever used with the MIT AI
   Lab PDP-10.  Since the device name DC is alreay claimed, we call
   this AI.
*/

#include "kx10_defs.h"

#ifndef NUM_DEVS_AI
#define NUM_DEVS_AI 0
#endif


#if (NUM_DEVS_AI > 0)

/* Disk pack geometry.  The track format is software defined.  ITS and
   SALV makes it hold two sectors with 1024 words of regular data and
   4 extra words. */
#define SECTOR_SIZE        1024
#define SECTORS            2
#define SURFACES           20
#define MEMOREX_CYLINDERS  203
#define CALCOMP_CYLINDERS  (2 * MEMOREX_CYLINDERS)
#define CYLINDER_SIZE      (SECTOR_SIZE * SECTORS * SURFACES)
#define MEMOREX_SIZE       (CYLINDER_SIZE * MEMOREX_CYLINDERS)
#define CALCOMP_SIZE       (CYLINDER_SIZE * CALCOMP_CYLINDERS)

/* The real sector size, including 2 header words, 4 extra data words,
   and 2 checksum words. */
#define SECTOR_REAL_SIZE   (SECTOR_SIZE + 8)
/* A track actually has some more free space.  Cylinder 0, surface 0
   has a readin block there. */
#define TRACK_REAL_SIZE    ((SECTORS + 1) * SECTOR_REAL_SIZE)
#define CYLINDER_REAL_SIZE (SURFACES * TRACK_REAL_SIZE)

#define AI_DEVNUM       0610    /* First device number; 614 also used. */
#define AI_NAME         "AI"
#define NUM_UNITS       16      /* Hardware units, but ITS only supports 8. */

/* All bit definitions are from the ITS file SYSTEM; DC10 DEFS27. */

/* CONI DC0 */
#define DASSGN  0400000000000LL /* ASSIGNED TO PROC (WITH SWITCH) */
#define DPIRQC  0400000 /* PI REQ BEING GENERATED */
#define DSSRQ   0200000 /* SEEK REQUEST */
#define DSDEEB  0010000 /* ENABLE INTERRUPT ON DATA ERROR OR READ/ COMP ERROR */
#define DSSERR  0004000 /* ERROR FLAG */
#define DSSAEB  0002000 /* ATTENTION ENABLE FLAG */
#define DSSATT  0001000 /* ATTENTION FLAG */
#define DSIENB  0000400 /* IDLE FLAG ENABLE */
#define DSSRUN  0000200 /* RUN */
#define DSSACT  0000100 /* ACTIVE */
#define DSSCEB  0000040 /* CHANNEL ENABLE */
#define DSSCHF  0000020 /* CHANNEL FLAG */
#define DSSCFL  0000010 /* CPU FLAG */

/* CONO DC0 */
#define DCSET   0400000 /* SET SELECTED */
#define DCCLR   0200000 /* CLEAR SELECTED */
#define DCCSET  0600000 /* RESET CONTROLLER THEN SET SELECTED */
#define DCDENB  0010000 /* DATA ERROR ENABLE */
#define DCERR   0004000 /* SET ERROR FLAG OR CLEAR ALL ERRORS */
#define DCATEB  0002000 /* ATTENTION ENABLE */
#define DCCATT  0001000 /* CLEAR ATTENTION */
#define DCSSRQ  0001000 /* SET SEEK REQUEST */
#define DCIENB  0000400 /* IDLE ENABLE */
#define DCSTAR  0000200 /* START (SET) */
#define DCSSTP  0000200 /* STOP (CLEAR) */
#define DCSGL   0000100 /* DO SINGLE COMMAND */
#define DCCENB  0000040 /* CHANNEL ENABLE */
#define DCCFLG  0000020 /* CHANNEL FLAG */
#define DCCPUF  0000010 /* CPU FLAG */

/* Bits to set or clear with DCSET or DCCLR. */
#define SET_MASK (DCDENB|DCERR|DCATEB|DCIENB|DCSTAR)
#define CLEAR_MASK (DCDENB|DCERR|DCATEB|DCCATT|DCIENB|DCSSTP)

/* CONI DC1 */
#define DIPE    04000   /* INTERNAL PARITY ERROR */
#define DRLNER  02000   /* RECORD LENGTH */
#define DRCER   01000   /* READ COMPARE ERROR */
#define DOVRRN  00400   /* OVERRUN */
#define DCKSER  00200   /* CKSUM OR DECODER ERR */
#define DWTHER  00100   /* WATCHDOG TIMER */
#define DFUNSF  00040   /* FILE UNSAFE, SEEK INCOMPLETE OR END OR DSK */
#define DOFFL   00020   /* OFF LINE OR MULT SEL */
#define DPROT   00010   /* WRT KEY OR RD ONLY OR PROTECT */
#define DDOBSY  00004   /* DATAO WHEN BSY */
#define DNXM    00002   /* NON-EX MEM */
#define DCPERR  00001   /* CORE PARITY ERR */

/* Channel commands */
#define DUNENB  0020000000000LL /* ENABLE LOAD UNIT FIELD */
#define DCMD    0740000000000LL
#define DCOPY   0040000000000LL /* COPY */
#define DCCOMP  0100000000000LL /* COMPARE */
#define DCSKIP  0140000000000LL /* SKIP */
#define DOPR    0200000000000LL /* BASIC OPR */
#define DSDRST  0240000000000LL /* STORE DRIVE STATUS */
#define DALU    0300000000000LL /* BASIC ALU OP CODE */
#define DRC     0400000000000LL /* READ COMPARE */
#define DWRITE  0440000000000LL /* WRITE */
#define DREAD   0500000000000LL /* READ */
#define DSEEK   0540000000000LL /* SEEK */
#define DRCC    0600000000000LL /* READ COMPARE CONTINUOUS */
#define DWRITC  0640000000000LL /* WRITE CONTINUOUS */
#define DREADC  0700000000000LL /* READ CONTINUOUS */
#define DSPC    0740000000000LL /* Special command. */

#define DHLT    0               /* 0 IN 4.9-4.5 = JUMP AND IN 3.5,3.6 = HALT */
#define DXCT    0000020000000LL /* XCT */
#define DJMP    0000040000000LL /* JUMP */
#define DJSR    0000060000000LL /* JSR */
#define DJMASK  0000060000000LL

/* OPR */
#define DOHXFR  0400000000LL    /* HALT DURING XFER (SO MB WILL BE SAFE) */

/* Special command, E condition (wait). */
#define DSWIDX  0020000000LL    /* WAIT UNTIL INDEX PULSE */
#define DSWSEC  0040000000LL    /* WAIT UNTIL SECTOR PULSE */
#define DSWINF  0060000000LL    /* NEVER (USE WITH G=3 OR 7) */
/* Special command, F condition (other wait). */
#define DSWNUL  0014000000LL    /* NO WAIT */
/* Special command, G operation. */
#define DSCRHD  0200000000LL    /* READ HEADER WORDS */
#define DSRCAL  0300000000LL     /* (RECALIBRATE) */
#define DSCWIM  0500000000LL    /* WRITE IMAGE */

/* ALU */
#define DLCC    010000000LL     /* OP FROM CC, STORE IN CC */
#define DLDBWC  030000000LL     /* OP A FROM DB, STORE IN WC */

#define WC      0037774000000LL /* Word count. */
#define ADDR    0000003777777LL /* Address field. */

/* Drive status. */

#define DDSWC   040000000LL             /* WRITE CURRENT SENSED */
#define DDSUNS  020000000LL             /* DRIVE UNSAFE */
#define DDSRDO  010000000LL             /* READ ONLY */
#define DDSSIC  004000000LL             /* SEEK INCOMPLETE */
#define DDSRDY  002000000LL             /* DRIVE READY */
#define DDSONL  001000000LL             /* DRIVE ON LINE */
#define DDSSEL  000400000LL             /* DRIVE SELECTED */

enum {
    MODE_ERROR = 0,
    MODE_WRITE,                 /* Write sector data. */
    MODE_READ,                  /* Read sector data. */
    MODE_READ_HEADERS,          /* Read sector headers. */
    MODE_COMPARE,               /* Compare sector data. */
    MODE_IMAGE                  /* Write raw image. */
};

enum image_state {
    IMAGE_GAP,                  /* Empty bits (ones) between sectors. */
    IMAGE_PREAMBLE,             /* Bit pattern before sector header. */
    IMAGE_HEADER,               /* Sector header, in FM encoding. */
    IMAGE_POSTAMBLE,            /* Empty bits (ones). */
    IMAGE_POSTAMBLE2,           /* A "01" to start the sector data. */
    IMAGE_SECTOR,               /* Sector data, in FM encoding. */
    IMAGE_ERROR,
};

static enum image_state image_state = IMAGE_ERROR;
static int image_count, image_sector_length;

static t_stat ai_devio(uint32 dev, uint64 *data);
static t_stat ai_svc(UNIT *);
static t_stat ai_reset(DEVICE *);
static t_stat ai_attach(UNIT *, CONST char *);
static t_stat ai_detach(UNIT *);
static t_stat ai_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag,
                       const char *cptr);
static const char *ai_description (DEVICE *dptr);


UNIT ai_unit[] = {
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
    { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
                 UNIT_ROABLE, CALCOMP_SIZE) },
};

DIB ai_dib = { AI_DEVNUM, 2, &ai_devio, NULL };

MTAB ai_mod[] = {
    {0}
};

DEBTAB ai_debug[] = {
    {"IRQ", DEBUG_IRQ, "Debug IRQ requests"},
    {"CMD", DEBUG_CMD, "Show command execution to devices"},
    {"DATA", DEBUG_DATA, "Show data transfers"},
    {"DETAIL", DEBUG_DETAIL, "Show details about device"},
    {"EXP", DEBUG_EXP, "Show exception information"},
    {"CONI", DEBUG_CONI, "Show CONI instructions"},
    {"CONO", DEBUG_CONO, "Show CONO instructions"},
    {"DATAIO", DEBUG_DATAIO, "Show DATAI and DATAO instructions"},
    {0, 0}
};

static UNIT *channel_unit = ai_unit;
static int latency_unit = 0;
static int channel_pc = 0;
static int channel_status = 0;
static uint64 channel_errors = 0;
static int channel_cc = 0;
static int channel_wc = 0;
static int channel_mode = MODE_ERROR;
static int channel_delay;
static int channel_default_delay = 1000;
static int channel_seek_initial = 25000; /* Milliseconds. */
static int channel_seek_delay = 500; /* Per cylinder travelled. */
static int channel_cylinder = 0;

REG ai_reg[] = {
    {ORDATA(PC, channel_pc, 20)},
    {ORDATA(STS, channel_status, 18)},
    {ORDATA(ERR, channel_errors, 12)},
    {ORDATA(CC, channel_cc, 20)},
    {ORDATA(WC, channel_wc, 12)},
    {ORDATA(SI, channel_seek_initial, 32)},
    {ORDATA(SD, channel_seek_delay, 32)},
    {ORDATA(CYL, channel_cylinder, 9)},
    {0}
};

DEVICE ai_dev = {
    AI_NAME, ai_unit, ai_reg, ai_mod,
    NUM_UNITS, 8, 18, 1, 8, 36,
    NULL, NULL, &ai_reset, NULL, &ai_attach, &ai_detach,
    &ai_dib, DEV_DISABLE | DEV_DEBUG | DEV_DIS, 0, ai_debug,
    NULL, NULL, &ai_help, NULL, NULL, &ai_description
};

static void clear_interrupt (void)
{
    if ((channel_status & (DSDEEB|DSSERR)) == (DSDEEB|DSSERR)
        || (channel_status & (DSSAEB|DSSATT)) == (DSSAEB|DSSATT)
        || (channel_status & (DSIENB|DSSRUN)) == DSIENB) {
        channel_status |= DPIRQC;
        sim_debug(DEBUG_IRQ, &ai_dev, "Set interrupt: %06o\n", channel_status);
        set_interrupt (AI_DEVNUM, channel_status);
    } else {
        channel_status &= ~DPIRQC;
       sim_debug(DEBUG_IRQ, &ai_dev, "Clear interrupt\n");
        clr_interrupt (AI_DEVNUM);
    }
}

static void channel_error (int errors)
{
    channel_errors |= errors;
    channel_status |= DSSERR;
    if (channel_status & DSDEEB) {
        channel_status |= DPIRQC;
        sim_debug(DEBUG_IRQ, &ai_dev, "Set error interrupt\n");
        set_interrupt (AI_DEVNUM, channel_status);
    }
}

static void channel_seek (const char *cmd, uint64 data, int offset)
{
    int cyl, sur, sec, x;
    int da;

    if (data & DUNENB)
        channel_unit = &ai_unit[(data >> 033) & 017];

    cyl = (data >> 11) & 0777;
    sur = (data >> 6) & 037;
    sec = data & 077;

    if (cyl >= CALCOMP_CYLINDERS && sur >= SURFACES && sec >= SECTORS) {
        sim_debug(DEBUG_EXP, &ai_dev, "Seek outside geometry\n");
        channel_error (DOVRRN);
        return;
    }

    da = SECTOR_REAL_SIZE * sec;
    da += TRACK_REAL_SIZE * sur;
    da += CYLINDER_REAL_SIZE * cyl;
    da += offset;
    if (channel_unit->flags & UNIT_ATT) {
        (void)sim_fseek(channel_unit->fileref, da * sizeof(uint64), SEEK_SET);
        x = channel_cylinder - cyl;
        if (x < 0)
            x = -x;
        if (x > 0)
            channel_delay = channel_seek_initial + x * channel_seek_delay;
        channel_cylinder = cyl;
        sim_debug(DEBUG_CMD, &ai_dev, "%s: unit %d seek %d (%d,%d,%d)\n",
                  cmd, (int)(channel_unit - ai_unit), channel_delay,
                  cyl, sur, sec);
    } else {
        sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
        channel_error (DOFFL);
    }
}

static void channel_special (uint64 data)
{
    if (data & DUNENB)
        channel_unit = &ai_unit[(data >> 033) & 017];

    switch (data & 0700000000LL) {
    case DSCRHD:
        channel_mode = MODE_READ_HEADERS;
        channel_seek ("READ HEADER WORDS", data, 0);
        break;
    case DSRCAL:
        sim_debug(DEBUG_CMD, &ai_dev, "Command: (RECALIBRATE)\n");
        channel_status |= DSSATT;
        channel_errors &= ~(017LL << 036);
        channel_errors |= (channel_unit - ai_unit) << 036;
        if (channel_status & DSSAEB) {
            channel_status |= DPIRQC;
            sim_debug(DEBUG_IRQ, &ai_dev, "Set attention interrupt\n");
            set_interrupt (AI_DEVNUM, channel_status);
        }
        break;
    case DSCWIM:
        if ((channel_unit->flags & UNIT_ATT) == 0) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
            channel_error (DOFFL);
        } else if (channel_unit->flags & UNIT_RO) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n");
            channel_error (DPROT);
        } else {
            channel_mode = MODE_IMAGE;
            image_state = IMAGE_GAP;
            image_count = 0;
            channel_seek ("WRITE IMAGE", data, 0);
        }
        break;
    default:
        sim_debug(DEBUG_CMD, &ai_dev, "(unknown special: %012llo)\n", data);
        break;
    }
}

static void channel_alu (uint64 data)
{
    switch (data & 034000000LL) {
    case DLCC:
        channel_cc = data & ADDR;
        sim_debug(DEBUG_CMD, &ai_dev, "ALU: OP FROM CC, STORE IN CC: %o\n", channel_cc);
        break;
    case DLDBWC:
        channel_wc = data & 07777;
        sim_debug(DEBUG_CMD, &ai_dev, "ALU: OP A FROM DB, STORE IN WC: %o\n", channel_wc);
        break;
    default:
        sim_debug(DEBUG_CMD, &ai_dev, "ALU: (unkownn)\n");
        break;
    }
}

static void print_data (uint64 *data, int n)
{
    int i;
    for (i = 0; i < n; i++)
        sim_debug(DEBUG_DATA, &ai_dev, "Data %012llo\n",
                  *data++);
}

static t_stat sim_fcompare (void *x, size_t m, size_t n, FILE *f)
{
    static uint64 buf[10240];

    if ((channel_unit->flags & UNIT_ATT) == 0) {
        sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
        channel_error (DOFFL);
        return SCPE_OK;
    }

    (void)sim_fread (buf, m, n, f);
    sim_debug(DEBUG_DATA, &ai_dev, "Memory contents:\n");
    print_data ((uint64 *)x, n);
    sim_debug(DEBUG_DATA, &ai_dev, "Disk contents:\n");
    print_data (buf, n);
    if (memcmp (x, buf, m * n) != 0) {
        sim_debug(DEBUG_EXP, &ai_dev, "Compare failed.\n");
        channel_error (DRCER);
    }
    return SCPE_OK;
}

/* The WRITE IMAGE command writes the sector headers as 56 continuous
   bits.  However, the READ HEADERS command presents them as 28-bit
   halves, each right aligned in a 36-bit word.  The image file stores
   the first format, so here we need to split the words apart.  Also
   skip over the sector data to get to next header. */
static t_stat sim_freadh (uint64 *x, size_t n, FILE *f)
{
    uint64 buf[2];
    size_t i;

    if ((channel_unit->flags & UNIT_ATT) == 0) {
        sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
        channel_error (DOFFL);
        return SCPE_OK;
    }

    for (i = 0; i < n; i++) {
        if ((i & 1) == 0) {
            (void)sim_fread (buf, sizeof (uint64), 2, f);
            (void)sim_fseek(f, (SECTOR_REAL_SIZE-2) * sizeof(uint64), SEEK_CUR);
            x[i] = buf[0] >> 8;
        } else {
            x[i] = (buf[0] & 0377) << 20;
            x[i] |= buf[1] >> 16;
        }
    }

    return SCPE_OK;
}

/* The track data fields are in somthing close to FM encoding.  Here
   we decode three bits at a time to yeild two bits of data.  When 36
   data bits have been decoded, output a word to the image file. */
static void decode_fm (int bit, FILE *f)
{
    static int state = 0;
    static uint64 word = 0;
    static int n = 0;
    static int bits = 1;

    bits = (bits << 1) + bit;
    state++;

    if (state != 3)
        return;

    word <<= 2;

    switch (bits & 017) {
    case 005:
    case 007:
        word |= (bits >> 4) & 2;
        word |= (bits >> 1) & 1;
        break;
    case 012:
    case 016:
        break;
    case 013:
    case 015:
    case 017:
        word |= (bits >> 1) & 3;
        break;
    default:
        sim_debug(DEBUG_EXP, &ai_dev, "Error in FM encoding: %o\n", bits);
        channel_error (DCKSER);
        break;
    }

    state = 0;
    n += 2;

    //sim_debug(DEBUG_DETAIL, &ai_dev, "FM: %o, %d, %012llo\n",
    //          bits, n, word);

    if (n == 36) {
        //sim_debug(DEBUG_DETAIL, &ai_dev, "Data: %012llo\n", word);
        (void)sim_fwrite (&word, sizeof word, 1, f);
        n = 0;
        word = 0;
    }
}

/* Decode a bit stream from the WRITE IMAGE command. */
static void decode_bit (int bit, FILE *f)
{
    static const int preamble_bits[] = { 1, 0, 1, 0, 1 };

    //sim_debug(DEBUG_DETAIL, &ai_dev, "Image: bit %o\n", bit);

    switch (image_state) {
    case IMAGE_GAP:
        if (bit == 0) {
            sim_debug(DEBUG_DETAIL, &ai_dev, "Image: %d gap bits\n",
                      image_count);
            image_state = IMAGE_PREAMBLE;
            image_count = 0;
        } else {
            image_count++;
        }
        break;
    case IMAGE_PREAMBLE:
        if (bit != preamble_bits[image_count % 5]) {
            sim_debug(DEBUG_DETAIL, &ai_dev, "Image: error in preamble bit %d\n",
                      image_count);
            image_state = IMAGE_ERROR;
            break;
        }
        image_count++;
        if (image_count == 5*8) {
            sim_debug(DEBUG_DETAIL, &ai_dev, "Image: preamble ok\n");
            image_state = IMAGE_HEADER;
            image_count = 0;
        }
        break;
    case IMAGE_HEADER:
        decode_fm (bit, f);
        image_count++;
        if (image_count == 108) {
            t_offset pos;
            uint64 header[2];
            image_state = IMAGE_POSTAMBLE;
            image_count = 0;
            pos = sim_ftell (channel_unit->fileref);
            (void)sim_fseeko(channel_unit->fileref, pos - 2 * sizeof(uint64), SEEK_SET);
            (void)sim_fread(header, sizeof(uint64), 2, channel_unit->fileref);
            (void)sim_fseeko(channel_unit->fileref, pos, SEEK_SET);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: key %03llo\n",
                      (header[0] >> 28) & 0377);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: cylinder %lld\n",
                      (header[0] >> 19) & 0777);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: surface %lld\n",
                      (header[0] >> 14) & 037);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: sector %lld\n",
                      (header[0] >> 8) & 077);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: indirect %llo\n",
                      (header[0] >> 7) & 1);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: software protect %llo\n",
                      (header[0] >> 6) & 1);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: hardware protect %llo\n",
                      (header[0] >> 5) & 1);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: parity %llo\n",
                      header[0] & 3);
            image_sector_length = 040000 - ((header[1] >> 16) & 037777);
            sim_debug(DEBUG_DETAIL, &ai_dev, "Header: length %o\n",
                      image_sector_length);
            if (image_sector_length > 02004) {
                  sim_debug(DEBUG_EXP, &ai_dev, "Record length error\n");
                  channel_error (DRLNER);
                  image_state = IMAGE_SECTOR;
            }
            image_sector_length += 2; /* Checksum */
            image_sector_length *= 54; /* 36-bit words, FM coded. */
        }
        break;
    case IMAGE_POSTAMBLE:
      image_count++;
      if (bit == 0) {
          sim_debug(DEBUG_DETAIL, &ai_dev, "Image: %d gap bits\n",
                    image_count);
          image_state = IMAGE_POSTAMBLE2;
          image_count = 0;
      }
      break;
    case IMAGE_POSTAMBLE2:
      if (bit == 0) {
          sim_debug(DEBUG_DETAIL, &ai_dev, "Image: error in postamble\n");
          image_state = IMAGE_ERROR;
      } else {
          image_state = IMAGE_SECTOR;
      }
      break;
    case IMAGE_SECTOR:
      decode_fm (bit, f);
      image_count++;
      if (image_count == image_sector_length) {
          image_state = IMAGE_GAP;
          image_count = 0;
      }
      break;
    case IMAGE_ERROR:
      break;
    }
}

static void decode_image (uint64 *data, int n, FILE *f)
{
  int i, j;
  for (i = 0; i < n; i++) {
      for (j = 35; j >= 0; j--)
          decode_bit ((int)(*data >> j) & 1, f);
      data++;
  }
}

static int check_nxm (uint64 data, int *n, uint64 *data2, int *n2)
{
    unsigned int addr = data & ADDR;
    *data2 = 0;
    *n2 = 0;
    if (addr + *n > MEMSIZE) {
        if (MEMSIZE < (ADDR+1)) {
            sim_debug(DEBUG_EXP, &ai_dev, "Access outside core memory\n");
            *n = MEMSIZE - addr;
            channel_error (DNXM);
            return 1;
        } else {
            /* Access wraps around 21-bit address. */
            *n2 = addr + *n - MEMSIZE;
            *data2 = 0;
            *n = MEMSIZE - addr;
            return 0;
        }
    }
    return 0;
}

/* Execute one channel instruction.  It may come from a channel
   program in core, or from a DATAO DC0, */
static void channel_command (uint64 data)
{
    struct timespec ts;
    int latency_timer;
    int n, n2;
    uint64 data2;
    int nxm = 0;

    if ((data & (DCMD|DUNENB)) == 0) {
        switch (data & DJMASK) {
        case DHLT:
            sim_debug(DEBUG_CMD, &ai_dev, "Command: DHLT\n");
            channel_status &= ~(DSSRUN|DSSACT);
            if (channel_status & DSIENB) {
                channel_status |= DPIRQC;
                sim_debug(DEBUG_IRQ, &ai_dev, "Set idle interrupt\n");
                set_interrupt (AI_DEVNUM, channel_status);
            }
            break;
        case DXCT:
            sim_debug(DEBUG_CMD, &ai_dev, "Command: XCT\n");
            break;
        case DJMP:
            channel_status |= DSSRUN|DSSACT;
            sim_activate(ai_unit, channel_default_delay);
            clear_interrupt ();
            if ((data & 014000000LL) == 004000000LL) {
              sim_debug(DEBUG_CMD, &ai_dev, "Command: JUMP DAOJNC: %o\n",
                        channel_cc);
              channel_cc++;
              if (channel_cc != ADDR+1)
                channel_pc = data & ADDR;
            } else {
                sim_debug(DEBUG_CMD, &ai_dev, "Command: JUMP\n");
                channel_pc = data & ADDR;
            }
            break;
        case DJSR:
            sim_debug(DEBUG_CMD, &ai_dev, "Command: JSR\n");
            n = 1;
            if (check_nxm (data, &n, &data2, &n2))
                break;
            M[data & ADDR] = (channel_pc + (channel_unit - ai_unit)) << 036;
            channel_pc++;
            channel_status |= DSSRUN|DSSACT;
            sim_activate(ai_unit, channel_default_delay);
            break;
        }
        return;
    }

    switch (data & DCMD) {
    case DCOPY:
        n = (data & WC) >> 20;
        if (n == 0)
            n = channel_wc;
        n = 010000 - n;
        sim_debug(DEBUG_CMD, &ai_dev, "COPY %d words to/from %012llo.\n",
                  n, data & ADDR);
        if ((channel_unit->flags & UNIT_ATT) == 0) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
            channel_error (DOFFL);
            break;
        }
        nxm = check_nxm (data, &n, &data2, &n2);
        switch (channel_mode) {
        case MODE_READ:
            (void)sim_fread (&M[data & ADDR], sizeof(uint64), n,
                             channel_unit->fileref);
            if (nxm)
                break;
            (void)sim_fread (&M[data2], sizeof(uint64), n2,
                             channel_unit->fileref);
            print_data (&M[data & ADDR], n);
            break;
        case MODE_READ_HEADERS:
            (void)sim_freadh (&M[data & ADDR], n, channel_unit->fileref);
            if (nxm)
                break;
            (void)sim_freadh (&M[data2], n2, channel_unit->fileref);
            break;
        case MODE_WRITE:
            if (channel_unit->flags & UNIT_RO) {
                sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n");
                channel_error (DPROT);
            } else {
                (void)sim_fwrite (&M[data & ADDR], sizeof(uint64), n,
                                  channel_unit->fileref);
                if (nxm)
                    break;
                (void)sim_fwrite (&M[data2], sizeof(uint64), n2,
                                  channel_unit->fileref);
            }
            break;
        case MODE_COMPARE:
            (void)sim_fcompare (&M[data & ADDR], sizeof(uint64), n,
                                channel_unit->fileref);
            if (nxm)
                break;
            (void)sim_fcompare (&M[data2], sizeof(uint64), n2,
                                channel_unit->fileref);
            /* If at the end of the sector, skip to next sector. */
            if ((sim_ftell (channel_unit->fileref) / sizeof(uint64))
                % SECTOR_REAL_SIZE == 1030)
              (void)sim_fseek(channel_unit->fileref, 4 * sizeof(uint64), SEEK_CUR);
            break;
        case MODE_IMAGE:
            decode_image (&M[data & ADDR], n, channel_unit->fileref);
            break;
        default:
            break;
        }
        break;
    case DCCOMP:
        n = (data & WC) >> 20;
        if (n == 0)
            n = channel_wc;
        n = 010000 - n;
        sim_debug(DEBUG_CMD, &ai_dev, "COMPARE %d words\n", n);
        if ((channel_unit->flags & UNIT_ATT) == 0) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n");
            channel_error (DOFFL);
            break;
        }
        nxm = check_nxm (data, &n, &data2, &n2);
        (void)sim_fcompare (&M[data & ADDR], sizeof(uint64), n,
                            channel_unit->fileref);
        if (nxm)
            break;
        (void)sim_fcompare (&M[data2], sizeof(uint64), n2,
                            channel_unit->fileref);
        break;
    case DCSKIP:
        n = 010000 - ((data & WC) >> 20);
        sim_debug(DEBUG_CMD, &ai_dev, "SKIP %o words\n", n);
        (void)sim_fseek(channel_unit->fileref, n * sizeof(uint64), SEEK_CUR);
        break;
    case DOPR:
        if (data & DOHXFR)
            sim_debug(DEBUG_CMD, &ai_dev, "OPR: Hang during xfer\n");
        else
            sim_debug(DEBUG_CMD, &ai_dev, "OPR ...\n");
        break;
    case DSDRST:
        if (data & DUNENB)
            channel_unit = &ai_unit[(data >> 033) & 017];
      
        sim_debug(DEBUG_CMD, &ai_dev,
                  "DSDRST, store unit %d status in %012llo.\n",
                  (int)(channel_unit - ai_unit), data & ADDR);

        n = 1;
        if (check_nxm (data, &n, &data2, &n2))
            break;

        (void)sim_rtcn_get_time (&ts, 0);
        latency_timer = ts.tv_nsec / 100000;
        latency_timer %= 254;
        M[data & ADDR] = latency_timer & 0377;
        M[data & ADDR] |= channel_cylinder << 8;
        if (channel_unit->flags & UNIT_ATT)
            /* Drive online. */
            M[data & ADDR] |= DDSONL;
        if (channel_unit->flags & UNIT_RO)
            /* Drive read-only. */
            M[data & ADDR] |= DDSRDO;
        break;
    case DALU:
        channel_alu (data);
        break;
    case DRC:
        channel_mode = MODE_COMPARE;
        channel_seek ("READ COMPARE", data, 2);
        break;
    case DWRITE:
        if (channel_unit->flags & UNIT_RO) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n");
            channel_error (DPROT);
            channel_mode = MODE_ERROR;
        } else {
            channel_mode = MODE_WRITE;
            channel_seek ("WRITE", data, 2);
        }
        break;
    case DREAD:
        channel_mode = MODE_READ;
        channel_seek ("READ", data, 2);
        break;
    case DSEEK:
        channel_seek ("SEEK", data, 2);
        break;
    case DRCC:
        channel_mode = MODE_COMPARE;
        channel_seek ("READ COMPARE CONTINUOUS", data, 2);
        break;
    case DWRITC:
        if (channel_unit->flags & UNIT_RO) {
            sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n");
            channel_error (DPROT);
            channel_mode = MODE_ERROR;
        } else {
            channel_mode = MODE_WRITE;
            channel_seek ("WRITE CONTINUOUS", data, 2);
        }
        break;
    case DREADC:
        channel_mode = MODE_READ;
        channel_seek ("READ CONTINUOUS", data, 2);
        break;
    case DSPC:
        channel_special (data);
        break;
    default:
        sim_debug(DEBUG_CMD, &ai_dev, "(unknown command: %012llo)\n", data);
        break;
    }
}

/* Process one channel instruction and update the channel state. */
static void channel_run (void)
{
  uint64 data, data2;
    int n = 1, n2;
    if (check_nxm (channel_pc, &n, &data2, &n2))
        return;
    data = M[channel_pc];
    //sim_debug(DEBUG_CMD, &ai_dev, "Channel PC=%06o %012llo\n",
    //          channel_pc, data);
    channel_pc++;
    channel_command (data);
}

t_stat ai_devio(uint32 dev, uint64 *data) {
    struct timespec ts;
    int latency_timer;

    switch(dev & 7) {
    case CONI:
        *data = channel_status;
        sim_debug(DEBUG_CONI, &ai_dev, "DC0, PC=%06o %012llo\n", PC, *data);
        return SCPE_OK;

    case CONO:
        sim_debug(DEBUG_CONO, &ai_dev, "DC0, PC=%06o %012llo\n", PC, *data);
        if ((*data & DCCSET) == DCCSET) {
          sim_debug(DEBUG_CMD, &ai_dev, "Reset controller then set selected.\n");
          ai_reset (&ai_dev);
        }
        channel_status &= ~7;
        channel_status |= *data & 7;
        if (*data & DCSET) {
          channel_status |= *data & SET_MASK;
          if (*data & DCSSRQ)
            channel_status |= DSSRQ;
          sim_debug(DEBUG_CMD, &ai_dev, "Set bits: %012llo -> %06o\n",
                    *data & SET_MASK, channel_status);
        } else if (*data & DCCLR) {
          channel_status &= ~(*data & CLEAR_MASK);
          sim_debug(DEBUG_CMD, &ai_dev, "Clear bits: %012llo -> %06o\n",
                    *data & CLEAR_MASK, channel_status);
          if (*data & DCERR)
            channel_errors = 0;
        }
        clear_interrupt ();
        return SCPE_OK;

    case DATAI:
        *data = 0;
        sim_debug(DEBUG_DATAIO, &ai_dev, "DATAI DC0, PC=%06o %012llo\n",
                  PC, *data);
        return SCPE_OK;

    case DATAO:
        sim_debug(DEBUG_DATAIO, &ai_dev, "DATAO DC0, PC=%06o %012llo\n",
                  PC, *data);
        if (channel_status & (DSSRUN|DSSACT)) {
            sim_debug(DEBUG_EXP, &ai_dev, "DATAO when busy\n");
            channel_error (DDOBSY);
        } else
            channel_command (*data);
        return SCPE_OK;

    case CONI|4:
        /* Latency timer, timer unit, attention unit. */
        (void)sim_rtcn_get_time (&ts, 0);
        latency_timer = ts.tv_nsec / 100000;
        latency_timer %= 254;
        *data = (latency_timer << 022)
          | (latency_unit << 032) | channel_errors;
        sim_debug(DEBUG_CONI, &ai_dev, "DC1, PC=%06o %012llo\n", PC, *data);
        return SCPE_OK;

    case CONO|4:
        sim_debug(DEBUG_CONO, &ai_dev, "DC1, PC=%06o %012llo\n", PC, *data);
        sim_debug(DEBUG_CMD, &ai_dev, "Latency timer set to unit %llo\n", *data);
        latency_unit = *data & 7;
        return SCPE_OK;

    case DATAI|4:
        *data = 0;
        sim_debug(DEBUG_DATAIO, &ai_dev, "DATAI DC1, PC=%06o %012llo\n",
                  PC, *data);
        return SCPE_OK;

    case DATAO|4:
        sim_debug(DEBUG_DATAIO, &ai_dev, "DATAO DC1, PC=%06o %012llo\n",
                  PC, *data);
        return SCPE_OK;
    }
    return SCPE_OK; /* Unreached */
}

t_stat ai_svc (UNIT *uptr)
{
    int i;
    channel_delay = channel_default_delay;
    for (i = 0; (channel_status & DSSRUN) && i < 10; i++)
        channel_run ();
    if (channel_status & DSSRUN)
        sim_activate(uptr, channel_delay);
    return SCPE_OK;
}

t_stat
ai_reset(DEVICE *dptr)
{
    channel_status = 0;
    channel_errors = 0;
    channel_pc = 0;
    channel_cc = 0;
    channel_wc = 0;
    channel_mode = 0;
    return SCPE_OK;
}

/* Device attach */
t_stat ai_attach (UNIT *uptr, CONST char *cptr)
{
    t_stat r;
    DEVICE *rptr;
    DIB *dib;

    r = attach_unit (uptr, cptr);
    if (r != SCPE_OK || (sim_switches & SIM_SW_REST) != 0)
        return r;
    rptr = find_dev_from_unit(uptr);
    if (rptr == 0)
        return SCPE_OK;
    dib = (DIB *) rptr->ctxt;
    set_interrupt(dib->dev_num, 0);
    return SCPE_OK;
}

/* Device detach */
t_stat ai_detach (UNIT *uptr)
{
    if (!(uptr->flags & UNIT_ATT))                          /* attached? */
        return SCPE_OK;
    if (sim_is_active (uptr))                              /* unit active? */
        sim_cancel (uptr);                                  /* cancel operation */
    return detach_unit (uptr);
}

t_stat ai_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "Systems Concepts DC-10\n\n");
fprint_set_help (st, dptr);
fprint_show_help (st, dptr);
fprint_reg_help (st, dptr);
return SCPE_OK;
}

const char *ai_description (DEVICE *dptr)
{
    return "Systems Concepts DC-10 disk controller";
}


#endif