/* i7080_chan.c: IBM 7080 Channel simulator

   Copyright (c) 2005-2016, Richard Cornwell

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICHARD CORNWELL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   channel

   The channel state for the IBM 705 channel is:
        705:    Polled mode transfer, unit record devices.
                Each chan_cmd will transfer one record.
        7621:   Basic data channel for 729 tapes.
        7908:   Channel to talk to disk, hypertape and data com.

   The 705 has 4 7621 channels.
   The status for these are kept in bank 2. Form as follow:
      Word 3 digits 7-6: 0
      Word 3 digit 5:  chan control digit.
      Word 3 digits 4-0: data buffer A.
      Word 2 digits 7-6: 0
      Word 2 digit 5:  chan control digit.
      Word 2 digits 4-0: data buffer B.
      Word 1 digits 7-4: 0
      Word 1 digits 3-0: Data Memory Address SMAC.
      Word 0 digits 7-4: Channel Program Status.
      Word 0 digits 3-0: Record Count/Program location.

   The 705 has 2 7908 channels.
   The status for these are kept in bank 4. Form as follow:
      Word 3 digits 7-0: 0
      Word 2 digits 7-0: 0
      Word 1 digits 7-4: 0
      Word 1 digits 3-0: Data Memory Address SMAC.
      Word 0 digits 7-4: Channel Program Status.
      Word 0 digits 3-0: Program location.

*/

#include "i7080_defs.h"

extern uint16       iotraps;
extern uint8        iocheck;
extern UNIT         cpu_unit;
extern uint16       IC;
extern uint8        AC[6 * 512];
extern int          chwait;
extern uint16       selreg;
extern uint16       selreg2;
extern uint16       flags;
extern uint32       MAC2;
extern uint16       irqflags;
extern uint8        ioflags[6200/8];

#define UNIT_V_MOD      (UNIT_V_UF + 4)
#define UNIT_V_HS       (UNIT_V_MOD + 1)
#define CHAN_MOD        (1 << UNIT_V_MOD)
#define CHAN_HS         (1 << UNIT_V_HS)



t_stat          set_chan_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat          chan_reset(DEVICE * dptr);
t_stat          chan_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag,
                        const char *cptr);
const char      *chan_description (DEVICE *dptr);


/* Channel data structures

   chan_dev     Channel device descriptor
   chan_unit    Channel unit descriptor
   chan_reg     Channel register list
   chan_mod     Channel modifiers list
*/

uint32              caddr[NUM_CHAN];            /* Channel memory address */
uint8               bcnt[NUM_CHAN];             /* Channel character count */
uint16              cmd[NUM_CHAN];              /* Current command */
uint16              irqdev[NUM_CHAN];           /* Device to generate interupts
                                                   for channel */
uint32              assembly[NUM_CHAN];         /* Assembly register */
uint32              chan_flags[NUM_CHAN];       /* Unit status */
extern uint8        inquiry;


#define READ_WRD        1
#define WRITE_WRD       2

const char     *chan_type_name[] = {
    "Polled", "Unit Record", "7621", "7908", "754"};


UNIT                chan_unit[] = {
    {UDATA(NULL, CHAN_SET | CHAN_S_TYPE(CHAN_UREC), 0)},
    /* Tape devices */
    {UDATA(NULL, CHAN_MOD|CHAN_SET|CHAN_S_TYPE(CHAN_7621), 0), 0, 0},   /* 20 */
    {UDATA(NULL, CHAN_MOD|CHAN_SET|CHAN_S_TYPE(CHAN_7621), 0), 0, 1},   /* 21 */
    {UDATA(NULL, CHAN_MOD|CHAN_SET|CHAN_S_TYPE(CHAN_7621), 0), 0, 2},   /* 22 */
    {UDATA(NULL, CHAN_MOD|CHAN_SET|CHAN_S_TYPE(CHAN_7621), 0), 0, 3},   /* 23 */
    /* 7080 High speed data channels */
    {UDATA(NULL, CHAN_HS | CHAN_SET | CHAN_S_TYPE(CHAN_7908), 0), 0, 0}, /* 40 */
    {UDATA(NULL, CHAN_HS | CHAN_SET | CHAN_S_TYPE(CHAN_7908), 0), 0, 1}, /* 41 */
    {UDATA(NULL, CHAN_SET | CHAN_S_TYPE(CHAN_7621), 0), 0, 4},  /* 44 */
    {UDATA(NULL, CHAN_SET | CHAN_S_TYPE(CHAN_7621), 0), 0, 5},  /* 45 */
    {UDATA(NULL, CHAN_SET | CHAN_S_TYPE(CHAN_7621), 0), 0, 6},  /* 46 */
    {UDATA(NULL, CHAN_SET | CHAN_S_TYPE(CHAN_7621), 0), 0, 7},  /* 47 */
};

REG                 chan_reg[] = {
    {BRDATA(ADDR, caddr, 10, 18, NUM_CHAN), REG_RO},
    {BRDATA(CMD, cmd, 8, 6, NUM_CHAN), REG_RO},
    {BRDATA(FLAGS, chan_flags, 2, 32, NUM_CHAN), REG_RO},
    {NULL}
};

MTAB                chan_mod[] = {
    {CHAN_MODEL, CHAN_S_TYPE(CHAN_UREC), "UREC", "UREC", &set_chan_type,
                NULL,NULL},
    {CHAN_MODEL, CHAN_S_TYPE(CHAN_754), "754", "754", &set_chan_type,
                NULL,NULL},
    {CHAN_MODEL, CHAN_S_TYPE(CHAN_7621), "7621", "7621", &set_chan_type,
                NULL,NULL},
    {CHAN_MODEL, CHAN_S_TYPE(CHAN_7908), "7908", NULL, NULL,NULL,NULL},
    {CHAN_HS, CHAN_HS, "HS", "HS", NULL,NULL,NULL},
    {MTAB_VUN, 0, "UNITS", NULL, NULL, &print_chan, NULL},
    {0}
};


/* Simulator debug controls */
DEBTAB              chn_debug[] = {
    {"CHANNEL", DEBUG_CHAN},
    {"TRAP", DEBUG_TRAP},
    {"CMD", DEBUG_CMD},
    {"DATA", DEBUG_DATA},
    {"DETAIL", DEBUG_DETAIL},
    {"EXP", DEBUG_EXP},
    {"SENSE", DEBUG_SNS},
    {"CH0", 0x0100 << 0},
    {"CH20", 0x0100 << 1},
    {"CH21", 0x0100 << 2},
    {"CH22", 0x0100 << 3},
    {"CH23", 0x0100 << 4},
    {"CH40", 0x0100 << 5},
    {"CH41", 0x0100 << 6},
    {"CH44", 0x0100 << 7},
    {"CH45", 0x0100 << 8},
    {"CH46", 0x0100 << 9},
    {"CH47", 0x0100 << 10},
    {0, 0}
};

DEVICE              chan_dev = {
    "CH", chan_unit, chan_reg, chan_mod,
    NUM_CHAN, 8, 15, 1, 8, 36,
    NULL, NULL, &chan_reset, NULL, NULL, NULL,
    NULL, DEV_DEBUG, 0, chn_debug,
    NULL, NULL, &chan_help, NULL, NULL, &chan_description
};


t_stat
set_chan_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc) {
    if ((uptr->flags & CHAN_MOD) == 0)
        return SCPE_ARG;
    uptr->flags &= ~CHAN_MODEL;
    uptr->flags |= val;
    return SCPE_OK;
}

t_stat
chan_reset(DEVICE * dptr)
{
    int                 i;

    /* Clear channel assignment */
    for (i = 0; i < NUM_CHAN; i++) {
        chan_flags[i] = 0;
        caddr[i] = 0;
        cmd[i] = 0;
        bcnt[i] = 0;
    }
    return chan_set_devs(dptr);
}

/* Map device to channel */
int
chan_mapdev(uint16 dev) {

    switch((dev >> 8) & 0xff) {
    case 0x02: return 1 + ((dev >> 4) & 0xf);   /* Map tapes to 20-23 */
    case 0x20:
               if (CHAN_G_TYPE(chan_unit[1].flags) == CHAN_754)
                   return -1;
               return 1;        /* Channel 20 */
    case 0x21:
               if (CHAN_G_TYPE(chan_unit[2].flags) == CHAN_754)
                   return -1;
               return 2;        /* Channel 21 */
    case 0x22:
               if (CHAN_G_TYPE(chan_unit[3].flags) == CHAN_754)
                   return -1;
               return 3;        /* Channel 22 */
    case 0x23:
               if (CHAN_G_TYPE(chan_unit[4].flags) == CHAN_754)
                   return -1;
               return 4;        /* Channel 23 */
    case 0x40: return 5;        /* HS Channel 40 */
    case 0x41: return 6;        /* HS Channel 41 */
    case 0x44: return 7;        /* Channel 44 */
    case 0x45: return 8;        /* Channel 45 */
    case 0x46: return 9;        /* Channel 46 */
    case 0x47: return 10;       /* Channel 47 */
    default:
             if (dev > 0x2000)  /* Invalid if over 2000 and not selected */
                return -1;
             return 0;
    }
}

/* Boot from given device */
t_stat
chan_boot(int32 unit_num, DEVICE * dptr)
{
    /* Set IAR = 1 (done by reset), channel to read one
        record to location 1 */
    UNIT               *uptr = &dptr->units[unit_num];
    int                 chan = UNIT_G_CHAN(uptr->flags);
    extern t_stat       cpu_reset(DEVICE *);

    cpu_reset(&cpu_dev);
    selreg = ((DIB *) dptr->ctxt)->addr + unit_num;
    chwait = chan + 1;  /* Force wait for channel */
    chan_flags[chan] |= STA_ACTIVE;
    chan_flags[chan] &= ~STA_PEND;
    cmd[chan] = 0;
    caddr[chan] = 0;
    return SCPE_OK;
}

t_stat
chan_issue_cmd(uint16 chan, uint16 dcmd, uint16 dev) {
    DEVICE            **dptr;
    DIB                *dibp;
    unsigned int        j;
    UNIT               *uptr;

    for (dptr = sim_devices; *dptr != NULL; dptr++) {
        int                 r;

        dibp = (DIB *) (*dptr)->ctxt;
        /* If no DIB, not channel device */
        if (dibp == 0)
            continue;
        uptr = (*dptr)->units;
        /* If this is a 7907 device, check it */
        if (dibp->ctype & CH_TYP_79XX) {
            for (j = 0; j < (*dptr)->numunits; j++, uptr++) {
                if (UNIT_G_CHAN(uptr->flags) == chan &&
                    (UNIT_SELECT & uptr->flags) == 0 &&
                    (dibp->addr & dibp->mask) == (dev & dibp->mask)) {
                    r = dibp->cmd(uptr, dcmd, dev);
                    if (r != SCPE_NODEV)
                        return r;
                }
            }
        /* Handle 7621 DS units */
        } else if (dibp->ctype & CH_TYP_76XX &&
                        (UNIT_G_CHAN(uptr->flags) == chan)) {
             r = dibp->cmd(uptr, dcmd, dev);
             if (r != SCPE_NODEV)
                 return r;
        /* Handle 754 and unit record devices */
        } else if ((dibp->addr & dibp->mask) == (dev & dibp->mask)) {
            if (dibp->upc == 1) {
                for (j = 0; j < (*dptr)->numunits; j++) {
                    if (UNIT_G_CHAN(uptr->flags) == chan) {
                        r = dibp->cmd(uptr, dcmd, dev);
                        if (r != SCPE_NODEV)
                            return r;
                    }
                    uptr++;
                }
            } else {
                if (UNIT_G_CHAN(uptr->flags) == chan) {
                    r = dibp->cmd(uptr, dcmd, dev);
                    if (r != SCPE_NODEV)
                        return r;
                }
            }
        }
    }
    return SCPE_NODEV;
}

/* Decrement the record count for a given channel, return 1 when
   no more records to send */
int chan_decr_reccnt(int chan) {
    int unit;

    unit = 512 + chan_unit[chan].u3 * 32;
    if (chan_dev.dctrl & (0x0100 << chan))
           sim_debug(DEBUG_DETAIL, &chan_dev, "chan %d reccnt %02o %02o %02o\n",
                 chan, AC[unit + 3], AC[unit + 2], AC[unit + 1]);
   if (AC[unit + 1] == 10 && AC[unit + 2] == 10 && AC[unit + 3] == 10)
        return 1;
    if (AC[unit + 1] != 10) {
        AC[unit + 1]--;
        if (AC[unit + 1] == 0)
           AC[unit + 1] = 10;
    } else {
        AC[unit + 1] = 9;
        if (AC[unit + 2] != 10) {
            AC[unit + 2]--;
            if (AC[unit + 2] == 0)
               AC[unit + 2] = 10;
        } else {
            AC[unit + 2] = 9;
            if (AC[unit + 3] != 10) {
                AC[unit + 3]--;
                if (AC[unit + 3] == 0)
                   AC[unit + 3] = 10;
            }
        }
   }
    if (chan_dev.dctrl & (0x0100 << chan))
           sim_debug(DEBUG_DETAIL, &chan_dev, "chan %d reccnt- %02o %02o %02o\n",
                 chan, AC[unit + 3], AC[unit + 2], AC[unit + 1]);
   if (AC[unit + 1] == 10 && AC[unit + 2] == 10 && AC[unit + 3] == 10)
        return 1;
   return 0;
}

/* Return true if record count is zero */
int chan_zero_reccnt(int chan) {
    int unit;

    unit = 512 + chan_unit[chan].u3 * 32;
    if (chan_dev.dctrl & (0x0100 << chan))
           sim_debug(DEBUG_DETAIL, &chan_dev, "chan %d reccnt %02o %02o %02o\n",
                 chan, AC[unit + 3], AC[unit + 2], AC[unit + 1]);
   if (AC[unit + 1] == 10 && AC[unit + 2] == 10 && AC[unit + 3] == 10)
        return 1;
   return 0;
}

/* Return next channel data address, advance address by 5 if channel */
uint32  chan_next_addr(int chan) {
    int         unit = 0;
    uint32      addr = 0;
    switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
    case CHAN_754:
    case CHAN_UREC:
        return ++caddr[chan];

    case CHAN_7621:
        unit = 8 + 512 + chan_unit[chan].u3 * 32;
        break;
    case CHAN_7908:
        unit = 8 + 1024 + chan_unit[chan].u3 * 32;
        break;
    }
    addr = load_addr(unit);
    store_addr(addr + 5, unit);
    return addr;
}

/* Execute the next channel instruction. */
void
chan_proc()
{
    int                 chan;
    int                 cmask;
    int                 unit;
    uint32              addr;

    /* Scan channels looking for work */
    for (chan = 0; chan < NUM_CHAN; chan++) {
        /* Skip if channel is disabled */
        if (chan_unit[chan].flags & UNIT_DIS)
            continue;

       /* If channel is disconnecting, do nothing */
        if (chan_flags[chan] & DEV_DISCO)
             continue;
        cmask = 0x0100 << chan;

        /* Check if RWW pending */
        if (chan_flags[chan] & STA_PEND) {
             /* If pending issue read command */
             chan_flags[chan] &= ~STA_PEND;
             if (selreg2 & 0x8000) {
                 int    chan2;
                 /* Find device on given channel and give it the command */
                 chan2 = chan_mapdev(selreg2 & 0x7fff);
                 if (chan2 < 0 || chan2 >= NUM_CHAN)
                     continue;
                 /* If no channel device, quick exit */
                 if (chan_unit[chan2].flags & UNIT_DIS ||
                      CHAN_G_TYPE(chan_unit[chan2].flags) != CHAN_754) {
                        flags |= 0x440; /* Set I/O Check */
                        selreg2 = 0;
                        continue;
                 }

                 /* Channel is busy doing something, wait */
                 if (chan_flags[chan2] & (DEV_SEL| DEV_DISCO|STA_TWAIT|STA_WAIT|
                                STA_ACTIVE)) {
                       chan_flags[chan] |= STA_PEND;
                       /* Try again */
                       continue;
                 }

                 /* Issue another command */
                 switch(chan_issue_cmd(chan2, IO_RDS, selreg2 & 0x7fff)) {
                 case SCPE_BUSY:        /* Try again */
                        chan_flags[chan] |= STA_PEND;
                        break;
                 case SCPE_NODEV:
                 case SCPE_IOERR:
                        /* Something wrong, stop */
                        flags |= 0x440; /* Set I/O Check */
                        selreg2 = 0;
                        break;
                 case SCPE_OK:
                        chan_flags[chan2] |= STA_ACTIVE;
                        selreg2 &= 0x7fff;
                        chwait = chan2+1;       /* Change wait channel */
                        break;
                  }
             } else {
                /* No pending, just store last address in MAC2 */
                  MAC2 = caddr[chan];
                  selreg2 = 0;
             }
             continue;
        }

        /* If channel not active, don't process anything */
        if ((chan_flags[chan] & STA_ACTIVE) == 0)
             continue;

        if ((chan_flags[chan] & (CTL_READ|CTL_WRITE)) &&
                (chan_flags[chan] & (CTL_END|SNS_UEND))) {
            if (chan_flags[chan] & DEV_SEL)
                chan_flags[chan] |= DEV_WEOR|DEV_DISCO;
            chan_flags[chan] &= ~(SNS_UEND|CTL_END|CTL_READ|CTL_WRITE);
        }

        /* If device requested attention, abort current command */
        if (chan_flags[chan] & CHS_ATTN) {
             if (chan_dev.dctrl & cmask)
                    sim_debug(DEBUG_EXP, &chan_dev, "chan %d Attn %d\n",
                              chan, irqdev[chan]);
             switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
             case CHAN_UREC:
             case CHAN_754:
                  if (selreg2 != 0)
                        chan_flags[chan] |= STA_PEND;
                  if (chan_flags[chan] & CHS_ERR)
                      flags |= 0x40; /* Set I/O Check */
                  break;
             case CHAN_7621:
             case CHAN_7908:
                  irqflags |= 1 << chan;
                  if (chan_dev.dctrl & cmask)
                      sim_debug(DEBUG_EXP, &chan_dev, "chan %d IRQ %x\n",
                              chan, irqdev[chan]);
                  break;
             }
             chan_flags[chan] &= ~(CHS_ATTN|STA_ACTIVE|STA_WAIT|DEV_WRITE);
             cmd[chan] &= ~CHAN_RECCNT;
             unit = irqdev[chan];
             if (chan_flags[chan] & CHS_EOF)
                 ioflags[unit/8] |= (1 << (unit & 07));
             flags |= 0x400; /* Set Any flag */
             /* Disconnect if selected */
             if (chan_flags[chan] & DEV_SEL)
                chan_flags[chan] |= (DEV_DISCO);
             continue;
        }

        /* If channel action all done, finish operation */
        if (((chan_flags[chan] & (DEV_SEL|STA_ACTIVE|STA_WAIT)) == STA_ACTIVE)
                 && (chan_flags[chan] & (CTL_CNTL|CTL_PREAD|CTL_PWRITE|CTL_READ|
                        CTL_WRITE|CTL_SNS)) == 0) {
            switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
            case CHAN_UREC:
            case CHAN_754:
                  if (selreg2 != 0)
                        chan_flags[chan] |= STA_PEND;
                  break;
            case CHAN_7621:
            case CHAN_7908:
                  irqflags |= 1 << chan;
                  if (chan_dev.dctrl & cmask)
                      sim_debug(DEBUG_EXP, &chan_dev, "chan %d IRQ %x\n",
                              chan, irqdev[chan]);
                  break;
            }
            chan_flags[chan] &= ~(STA_ACTIVE|DEV_WRITE);
            if (chan_flags[chan] & CHS_EOF) {
                if (chan_dev.dctrl & cmask)
                    sim_debug(DEBUG_EXP, &chan_dev, "chan %d EOF %x\n",
                              chan, irqdev[chan]);
                unit = irqdev[chan];
                ioflags[unit/8] |= (1 << (unit & 07));
                chan_flags[chan] &= ~CHS_EOF;
                chan_flags[chan] |= CHS_ERR;
                flags |= 0x400; /* Set Any flag */
            }
            continue;
        }

        switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
        case CHAN_UREC:
        case CHAN_754:
             /* If device put up EOR, terminate transfer. */
             if (chan_flags[chan] & (DEV_REOR|DEV_WEOR)) {
                  if (chan_dev.dctrl & cmask)
                         sim_debug(DEBUG_EXP, &chan_dev, "chan %d EOR\n",
                                   chan);
                  if (selreg2 != 0)
                        chan_flags[chan] |= STA_PEND;
                  chan_flags[chan] &= ~(STA_ACTIVE|STA_WAIT|DEV_WRITE|DEV_REOR);
                  /* Disconnect if selected */
                  if (chan_flags[chan] & DEV_SEL)
                     chan_flags[chan] |= (DEV_DISCO);
                  continue;
             }
             break;

        case CHAN_7621:
             /* Waiting on unit ready, or command */
             if (chan_flags[chan] & STA_WAIT) {
                if ((chan_flags[chan] & STA_TWAIT) == 0) {
                     /* Device ready, see if command under record count */
                     if (cmd[chan] & CHAN_CMD) {
                         /* Done if EOF set */
                         if (chan_flags[chan] & CHS_EOF) {
                              cmd[chan] &= ~(CHAN_RECCNT|CHAN_CMD);
                              chan_flags[chan] &= ~(STA_WAIT);
                              continue; /* On to next channel */
                         }

                         /* Issue another command */
                         switch(chan_issue_cmd(chan, 0xff & (cmd[chan] >> 9),
                                irqdev[chan])) {
                         case SCPE_BUSY:
                                continue;
                         case SCPE_OK:
                                /* If started, drop record count */
                                if (chan_decr_reccnt(chan)){
                                     cmd[chan] &= ~(CHAN_RECCNT|CHAN_CMD);
                                     chan_flags[chan] &= ~(STA_WAIT);
                                     continue;  /* On to next channel */
                                }
                                continue;
                         case SCPE_NODEV:
                         case SCPE_IOERR:
                             /* Something wrong, stop */
                                cmd[chan] &= ~(CHAN_RECCNT|CHAN_CMD);
                                break;
                         }
                         continue; /* On to next channel */
                     }
                     chan_flags[chan] &= ~(STA_WAIT);
                 }
                 continue;      /* On to next channel */
             }

             /* If device put up EOR, terminate transfer. */
             if (chan_flags[chan] & DEV_REOR) {
                  int ch;
                  if (chan_dev.dctrl & cmask)
                         sim_debug(DEBUG_EXP, &chan_dev, "chan %d EOR\n",
                                   chan);
                 /* If reading, check if partial word read */
                  if ((chan_flags[chan] & DEV_WRITE) == 0) {
                      unit = 512 + chan_unit[chan].u3 * 32;
                      unit += (cmd[chan] & CHAN_BFLAG)? 16: 24;
                      ch = AC[unit + 5];
                      if (ch != 10) {
                        /* Yes, fill with group marks and mark as full */
                          while(ch < 5)
                              AC[unit+ch++] = CHR_GM;
                          cmd[chan] |= (cmd[chan] & CHAN_BFLAG)?
                                     CHAN_BFULL: CHAN_AFULL;
                      }
                  }
                  if (cmd[chan] & CHAN_RECCNT) {
                      if (!chan_decr_reccnt(chan)) {
                           chan_flags[chan] &= ~DEV_REOR;
                           continue;
                      }
                      cmd[chan] &= ~CHAN_RECCNT;
                  }
                  chan_flags[chan] &= ~(DEV_REOR|DEV_WEOR);
                  /* Disconnect if selected */
                  if (chan_flags[chan] & DEV_SEL)
                     chan_flags[chan] |= (DEV_DISCO);
            }

            /* Channel gave us a Write EOR, terminate it needed. */
            if (chan_flags[chan] & DEV_WEOR) {
                  if (chan_dev.dctrl & cmask)
                         sim_debug(DEBUG_EXP, &chan_dev, "chan %d WEOR\n",
                                   chan);
                  if (cmd[chan] & CHAN_RECCNT) {
                      if (!chan_decr_reccnt(chan)) {
                           chan_flags[chan] &= ~DEV_WEOR;
                           cmd[chan] &= ~CHAN_END;
                           continue;
                      }
                      cmd[chan] &= ~CHAN_RECCNT;
                  }
                  chan_flags[chan] &= ~(DEV_WEOR|DEV_REOR);
                  /* Disconnect if selected */
                  if (chan_flags[chan] & DEV_SEL)
                     chan_flags[chan] |= (DEV_DISCO);
            }

            if ((chan_flags[chan] & DEV_WRITE) &&
                (cmd[chan] & (CHAN_AFULL|CHAN_BFULL)) !=
                         (CHAN_AFULL|CHAN_BFULL)) {
                char    ch;
                unit = 512 + chan_unit[chan].u3 * 32;
                if (cmd[chan] & CHAN_END)
                    break;
                addr = chan_next_addr(chan);
                if ((cmd[chan] & CHAN_AFULL) == 0) {
                        unit += 24;
                        cmd[chan] |= CHAN_AFULL;
                        ch = 'a';
                } else if ((cmd[chan] & CHAN_BFULL) == 0) {
                        unit += 16;
                        cmd[chan] |= CHAN_BFULL;
                        ch = 'b';
                } else {
                        break;
                }
                AC[unit] = M[addr++];
                AC[unit+1] = M[addr++];
                AC[unit+2] = M[addr++];
                AC[unit+3] = M[addr++];
                AC[unit+4] = M[addr++];
                AC[unit+5] = 10;
                  if (chan_dev.dctrl & cmask)
                         sim_debug(DEBUG_DATA, &chan_dev,
                                "chan %d (%d) > %c %02o%02o%02o%02o%02o\n",
                                chan, addr-5, ch,
                                AC[unit], AC[unit+1], AC[unit+2], AC[unit+3],
                                AC[unit+4]);

                if (cmd[chan] & CHAN_NOREC && (addr % 20000) == 0)
                   cmd[chan] |= CHAN_END;
                /* Wrap around if over end of memory, set error */
                if (addr > EMEMSIZE) {
                   chan_flags[chan] |= CHS_ERR;
                   /* addr -= EMEMSIZE; */
                   if (chan_dev.dctrl & cmask)
                       sim_debug(DEBUG_EXP, &chan_dev, "write wrap %d\n", chan);
                }
                break;
            }
            while ((chan_flags[chan] & DEV_WRITE) == 0&&
                (cmd[chan] & (CHAN_AFULL|CHAN_BFULL)) != 0) {
                char    ch;
                unit = 512 + chan_unit[chan].u3 * 32;
                addr = chan_next_addr(chan);
                if (cmd[chan] & CHAN_AFULL) {
                        unit += 24;
                        cmd[chan] &= ~CHAN_AFULL;
                        ch = 'a';
                } else if (cmd[chan] & CHAN_BFULL) {
                        unit += 16;
                        cmd[chan] &= ~CHAN_BFULL;
                        ch = 'b';
                } else {
                        break;
                }
                if ((cmd[chan]& CHAN_SKIP) == 0) {
                    M[addr++] = AC[unit];
                    M[addr++] = AC[unit+1];
                    M[addr++] = AC[unit+2];
                    M[addr++] = AC[unit+3];
                    M[addr++] = AC[unit+4];
                    if (addr > EMEMSIZE) {
                        cmd[chan] |= CHAN_SKIP;
                        chan_flags[chan] |= CHS_ERR;
                       if (chan_dev.dctrl & cmask)
                       sim_debug(DEBUG_EXP, &chan_dev, "read wrap %d\n", chan);
                    }
                    if (chan_dev.dctrl & cmask)
                         sim_debug(DEBUG_DATA, &chan_dev,
                                "chan %d (%d) < %c %02o%02o%02o%02o%02o\n",
                                 chan, addr-5, ch,
                                AC[unit], AC[unit+1], AC[unit+2], AC[unit+3],
                                AC[unit+4]);
                }
                AC[unit+5] = 10;
            }
            break;
        case CHAN_7908:
            switch(chan_flags[chan] & (DEV_WRITE|DEV_FULL)) {
            case 0:
            case DEV_WRITE|DEV_FULL:
                continue;
            case DEV_WRITE:
                if (cmd[chan] & CHAN_END)
                    break;
                unit = 1024 + chan_unit[chan].u3 * 32;
                addr = chan_next_addr(chan);
                assembly[chan] = M[addr++] & 077;
                assembly[chan] |= (M[addr++] & 077) << 6;
                assembly[chan] |= (M[addr++] & 077) << 12;
                assembly[chan] |= (M[addr++] & 077) << 18;
                assembly[chan] |= (M[addr++] & 077) << 24;
                if (cmd[chan] & CHAN_NOREC && (addr % 20000) == 19999)
                   cmd[chan] |= CHAN_END;
                bcnt[chan] = 0;
                chan_flags[chan] |= DEV_FULL;
                break;
            case DEV_FULL:
                unit = 1024 + chan_unit[chan].u3 * 32;
                addr = chan_next_addr(chan);
                if ((cmd[chan]& CHAN_SKIP) == 0) {
                    M[addr++] = assembly[chan] & 077;
                    M[addr++] = (assembly[chan] >> 6) & 077;
                    M[addr++] = (assembly[chan] >> 12) & 077;
                    M[addr++] = (assembly[chan] >> 18) & 077;
                    M[addr++] = (assembly[chan] >> 24) & 077;
                }
                if (addr > EMEMSIZE) {
                   cmd[chan] |= CHAN_SKIP;
                   chan_flags[chan] |= CHS_ATTN;
                }
                bcnt[chan] = 0;
                chan_flags[chan] &= ~DEV_FULL;
                break;
            }
            break;
        }
    }
}

void chan_set_attn_inq(int chan) {
/*    inquiry = 1; */
}

void chan_clear_attn_inq(int chan) {
/*    inquiry = 0; */
}


/* Issue a command to a channel */
int
chan_cmd(uint16 dev, uint16 dcmd, uint32 addr)
{
    int                 chan;
    int                 unit;
    t_stat              r;
    int                 op;

    /* Find device on given channel and give it the command */
    chan = chan_mapdev(dev);
    if (chan < 0 || chan >= NUM_CHAN)
        return SCPE_IOERR;
    /* If no channel device, quick exit */
    if (chan_unit[chan].flags & UNIT_DIS)
        return SCPE_IOERR;
    /* Unit is busy doing something, wait */
    if (chan_flags[chan] & (DEV_SEL| DEV_DISCO|STA_TWAIT|STA_WAIT|STA_ACTIVE))
        return SCPE_BUSY;

    /* Ok, try and find the unit */
    caddr[chan] = addr;
    assembly[chan] = 0;
    op = dcmd >> 8;
    cmd[chan] &= CHAN_RECCNT;   /* Clear everything but record count flag */
    cmd[chan] |= dcmd & CHAN_ZERO;
    if (op == IO_RDS && (dcmd & 0xf) != 0) {
        switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
        case CHAN_754:
        case CHAN_UREC:
            cmd[chan] |= CHAN_SKIP;
            break;
        case CHAN_7621:
            switch(dcmd & 0xf) {
            case 1:     cmd[chan] |= CHAN_SKIP; break;
            case 2:
                unit = 8+512 + chan_unit[chan].u3 * 32;
                M[addr++] = AC[unit++];
                M[addr++] = AC[unit++];
                M[addr++] = AC[unit++];
                M[addr++] = AC[unit++];
                cmd[chan] &= ~CHAN_RECCNT;
                return SCPE_OK;
            default:
                break;
            }
            break;
        case CHAN_7908:
            switch(dcmd & 0xf) {
            case 1:     cmd[chan] |= CHAN_SKIP;
            case 0:     chan_flags[chan] |= CTL_READ; break;
            case 3:     chan_flags[chan] |= CTL_SNS; break; /* Do Sense */
            case 4:     /* Do control then read */
                chan_flags[chan] |= CTL_CNTL|CTL_PREAD;
                break;
            }
            break;
        }
    }
    if (op == IO_WRS && (dcmd & 0xf) != 0) {
        switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
        case CHAN_754:
        case CHAN_UREC:
            cmd[chan] |= CHAN_NOREC;
            break;
        case CHAN_7621:
            dcmd &= ~ CHAN_ZERO;
            switch(dcmd & 0xf) {
            case 1:     cmd[chan] |= CHAN_NOREC; break;
            case 2:
                unit = 512 + chan_unit[chan].u3 * 32;
                addr /= 10;
                AC[unit+1] = addr % 10;
                if (AC[unit+1] == 0)
                   AC[unit+1] = 10;
                addr /= 10;
                AC[unit+2] = addr % 10;
                addr /= 10;
                if (AC[unit+2] == 0)
                   AC[unit+2] = 10;
                AC[unit+3] = addr % 10;
                if (AC[unit+3] == 0)
                   AC[unit+3] = 10;
                if (chan_dev.dctrl & (0x0100 << chan))
                           sim_debug(DEBUG_DETAIL, &chan_dev,
                                 "chan %d set reccnt %02o %02o %02o\n",
                                chan, AC[unit + 3], AC[unit + 2], AC[unit + 1]);
                cmd[chan] |= CHAN_RECCNT;
                return SCPE_OK;
            default:
                break;
            }
            break;
        case CHAN_7908:
            switch(dcmd & 0xf) {
            case 1:     cmd[chan] |= CHAN_NOREC;
            case 0:     chan_flags[chan] |= CTL_WRITE; break;
            case 3:     /* Do control */
                        chan_flags[chan] |= CTL_CNTL; break;
            case 4:     /* Do control then write */
                        chan_flags[chan] |= CTL_CNTL|CTL_PWRITE; break;
            }
            break;
        }
    }
    /* Handle initial record count of zero for special ops */
    if (CHAN_G_TYPE(chan_unit[chan].flags) == CHAN_7621 &&
                cmd[chan] & CHAN_RECCNT) {
        switch(op) {
        case IO_WEF:
        case IO_ERG:
        case IO_BSR:
                if (chan_zero_reccnt(chan)) {
                    /* Just check if unit ready */
                    r = chan_issue_cmd(chan, OP_TRS, dev);
                    if (r == SCPE_OK)
                       cmd[chan] &= ~CHAN_RECCNT;
                    return r;
                }
                break;
        default:        /* Don't worry about it, not effected by RC */
                break;
        }
    }

    chan_flags[chan] &= ~(CTL_CNTL|CTL_READ|CTL_WRITE|SNS_UEND|CTL_WRITE|
                                CTL_SNS|CHS_ATTN);

    r = chan_issue_cmd(chan, op, dev);
    if (r == SCPE_OK)
        chan_flags[chan] &= ~(CHS_EOF|CHS_ERR|CHS_ATTN);
    /* Activate channel if select raised */
    if (r == SCPE_OK && chan_flags[chan] & DEV_SEL) {
        chan_flags[chan] |= STA_ACTIVE;
        irqdev[chan] = dev;
        irqflags &= ~(1 << chan);
        ioflags[dev/8] &= ~(1 << (dev & 07));
        /* Set starting address. */
        switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
        case CHAN_754:
        case CHAN_UREC:
            if (op == IO_RDS) {
                if (selreg2 & 0x8000)
                    caddr[chan] = MAC2;
                selreg2 &= 0x7fff;
            }
            chwait = chan+1;
            break;
        case CHAN_7621:
            unit = 512 + chan_unit[chan].u3 * 32;
            AC[unit+16+5] = 10; /* Set digit next to 0 */
            AC[unit+24+5] = 10;
            store_addr(caddr[chan], 8 + unit);
            if (cmd[chan] & CHAN_RECCNT && chan_zero_reccnt(chan)) {
                cmd[chan] &= ~CHAN_RECCNT;
            }
            break;
        case CHAN_7908:
            store_addr(caddr[chan], 8+1024 + chan_unit[chan].u3 * 32);
            break;
        }
    }
    if (r == SCPE_OK && CHAN_G_TYPE(chan_unit[chan].flags) == CHAN_7621) {
        switch(op) {
        case IO_WEF:
        case IO_ERG:
        case IO_BSR:
            if (cmd[chan] & CHAN_RECCNT && chan_zero_reccnt(chan)) {
                cmd[chan] &= ~CHAN_RECCNT;
            }
            if (cmd[chan] & CHAN_RECCNT) {
                chan_decr_reccnt(chan);
                cmd[chan] &= CHAN_RECCNT;
                cmd[chan] |= (op << 9) | CHAN_CMD;
            }
            /* Fall through */

        case IO_SKR:
        case IO_BSF:
        case IO_REW:
        case IO_RUN:
            chan_flags[chan] |= STA_ACTIVE|STA_WAIT;
            irqdev[chan] = dev;
            irqflags &= ~(1 << chan);
            ioflags[dev/8] &= ~(1 << (dev & 07));
        default:
            break;
        }
    }
    return r;
}

/*
 * Process the CHR 3 13 command and abort all channel activity
 */
void
chan_chr_13()
{
    int                 chan;

    /* Scan channels looking for work */
    for (chan = 0; chan < NUM_CHAN; chan++) {
        /* Skip if channel is disabled */
        if (chan_unit[chan].flags & UNIT_DIS)
            continue;

       /* If channel is disconnecting, do nothing */
        if (chan_flags[chan] & DEV_DISCO)
             continue;

        /* If channel not active, don't process anything */
        if ((chan_flags[chan] & STA_ACTIVE) == 0)
             continue;

        if (chan_flags[chan] & DEV_SEL)
            chan_flags[chan] |= DEV_WEOR|DEV_DISCO;

        chan_flags[chan] &= ~(CHS_ATTN|STA_ACTIVE|STA_WAIT);
    }

    irqflags = 0;
}


/*
 * Write a word to the assembly register.
 */
int
chan_write(int chan, t_uint64 * data, int flags)
{
    return TIME_ERROR;
}

/*
 * Read next word from assembly register.
 */
int
chan_read(int chan, t_uint64 * data, int flags)
{
    return TIME_ERROR;
}

/*
 * Write a char to the assembly register.
 */
int
chan_write_char(int chan, uint8 * data, int flags)
{
    uint8       ch = *data;
    int         unit;
    uint16      msk;

    /* Based on channel type get next character */
    switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
    case CHAN_754:
    case CHAN_UREC:
        if (*data == 0)
           *data = 020;
        if (caddr[chan] > EMEMSIZE) {
           cmd[chan] |= CHAN_SKIP;
           chan_flags[chan] |= CHS_ATTN;
        }
        if ((cmd[chan] & CHAN_SKIP) == 0)
            M[caddr[chan]] = *data;
        caddr[chan]++;
        break;
    case CHAN_7621:
        if (*data == 0)
           *data = 020;
        /* If no data, then return timing error */
        if ((cmd[chan] & (CHAN_AFULL|CHAN_BFULL)) == (CHAN_AFULL|CHAN_BFULL))
              return TIME_ERROR;
        unit = 512 + chan_unit[chan].u3 * 32;
        if ((cmd[chan] & CHAN_BFLAG) == 0 && (cmd[chan] & CHAN_AFULL) == 0) {
                unit += 24;
                msk = CHAN_AFULL;
        } else if ((cmd[chan] & CHAN_BFLAG) && (cmd[chan] & CHAN_BFULL) == 0) {
                unit += 16;
                msk = CHAN_BFULL;
        } else { /* We are off sync, or something */
                /* We have data, so switch CHAN_BFLAG and try other buffer */
                cmd[chan] ^= CHAN_BFLAG;
                unit += (cmd[chan] & CHAN_BFLAG)? 16: 24;
                msk = (cmd[chan] & CHAN_BFLAG)? CHAN_BFULL: CHAN_AFULL;
                if (chan_dev.dctrl & (0x0100 << chan))
                           sim_debug(DEBUG_DETAIL, &chan_dev,
                                 "switching buffer %d\n", chan);
        }
        ch = AC[5 + unit];
        if (ch == 10)
            ch = 0;
        AC[unit + ch] = *data & 077;
        if (chan_dev.dctrl & (0x0100 << chan))
                   sim_debug(DEBUG_DATA, &chan_dev,
                                 "%d < %02o (%d)\n", chan, *data, ch);
        AC[5 + unit] = ch + 1;
        if (ch == 4) {
            cmd[chan] |= msk;
            cmd[chan] ^= CHAN_BFLAG;
        }
        break;
    case CHAN_7908:
        if (bcnt[chan] > 4)
           return TIME_ERROR;
        if (chan_flags[chan] & CTL_SNS) {
           *data &= 027;
           *data |= 040;
        } else {
           if (*data == 0)
                *data = 020;
        }
        assembly[chan] |= *data << (6 * bcnt[chan]);
        bcnt[chan]++;
        if (bcnt[chan] == 5)
           chan_flags[chan] |= DEV_FULL;
    }


    /* If device gave us an end, terminate transfer */
    if (flags & DEV_REOR) {
        chan_flags[chan] |= DEV_REOR|DEV_FULL;
        if (chan_flags[chan] & DEV_SEL)
            chan_flags[chan] |= DEV_DISCO;
        return END_RECORD;
        /* If over size of memory, terminate */
    } else if (!MEM_ADDR_OK(caddr[chan])) {
        chan_flags[chan] |= DEV_REOR|DEV_FULL;
        if (chan_flags[chan] & DEV_SEL)
            chan_flags[chan] |= DEV_DISCO;
        return END_RECORD;
    }

    return DATA_OK;
}

/*
 * Read next char from assembly register.
 */
int
chan_read_char(int chan, uint8 * data, int flags)
{
    uint8       ch;
    int         unit;
    uint16      msk;

    /* Check if he write out last data */
    if ((chan_flags[chan] & STA_ACTIVE) == 0)
        return TIME_ERROR;

    /* Based on channel type get next character */
    switch(CHAN_G_TYPE(chan_unit[chan].flags)) {
    case CHAN_754:
    case CHAN_UREC:
        *data = M[caddr[chan]];
        if (*data == CHR_BLANK)
            *data = CHR_ABLANK;
        if (cmd[chan] & CHAN_ZERO && *data != CHR_GM)
            M[caddr[chan]] = CHR_BLANK;
        if (chan_dev.dctrl & (0x0100 << chan))
                   sim_debug(DEBUG_DATA, &chan_dev,
                                 "%d > %02o (%d)\n", chan, *data, *data);
        caddr[chan]++;
        if ((cmd[chan] & CHAN_NOREC && (caddr[chan] % 19999) == 0)) {
            chan_flags[chan] |= DEV_WEOR;
            return END_RECORD;
        }
        break;
    case CHAN_7621:
        /* If no data, then return timing error */
        if ((cmd[chan] & (CHAN_AFULL|CHAN_BFULL)) == 0) {
              if (cmd[chan] & CHAN_END) {
                  chan_flags[chan] |= DEV_WEOR;
                  return END_RECORD;
              }
              return TIME_ERROR;
        }
        unit = 512 + chan_unit[chan].u3 * 32;
        if ((cmd[chan] & CHAN_BFLAG) == 0 && (cmd[chan] & CHAN_AFULL)) {
                unit += 24;
                msk = ~CHAN_AFULL;
        } else if ((cmd[chan] & CHAN_BFLAG) && (cmd[chan] & CHAN_BFULL)) {
                unit += 16;
                msk = ~CHAN_BFULL;
        } else { /* We are off sync, or something */
                /* We have data, so switch CHAN_BFLAG and try other buffer */
                cmd[chan] ^= CHAN_BFLAG;
                unit += (cmd[chan] & CHAN_BFLAG)? 16: 24;
                msk = (cmd[chan] & CHAN_BFLAG)? ~CHAN_BFULL: ~CHAN_AFULL;
                if (chan_dev.dctrl & (0x0100 << chan))
                           sim_debug(DEBUG_DETAIL, &chan_dev,
                                 "switching buffer %d\n", chan);
        }
        ch = AC[5 + unit];
        if (ch == 10)
            ch = 0;
        *data = AC[unit + ch] & 077;
        if (*data == CHR_BLANK)
            *data = CHR_ABLANK;
        if (chan_dev.dctrl & (0x0100 << chan))
                   sim_debug(DEBUG_DATA, &chan_dev,
                                 "%d > %02o (%d)\n", chan, *data, ch);
        AC[5 + unit] = ch + 1;
        if (ch == 4) {
            cmd[chan] &= msk;
            cmd[chan] ^= CHAN_BFLAG;
        }
        break;
    case CHAN_7908:
        if (bcnt[chan] > 4)
           return TIME_ERROR;
        *data = assembly[chan] >> (6 * bcnt[chan]);
        if (*data == CHR_BLANK)
            *data = CHR_ABLANK;
        if (chan_flags[chan] & CTL_CNTL && *data == CHR_GM) {
           chan_flags[chan] |= (chan_flags[chan] & (CTL_PREAD|CTL_PWRITE)) << 2;
           chan_flags[chan] &= ~(CTL_CNTL|CTL_PREAD|CTL_PWRITE);
           if (chan_flags[chan] & CTL_READ) {
                chan_next_addr(chan);
                chan_next_addr(chan);
           }
           bcnt[chan] = 0;
           return END_RECORD;
        }

        bcnt[chan]++;
        if (bcnt[chan] == 5)
           chan_flags[chan] &= ~DEV_FULL;
    }

    /* Check if we hit group mark */
    if ((cmd[chan] & CHAN_NOREC) == 0 && *data == CHR_GM) {
        chan_flags[chan] |= DEV_WEOR;
        return END_RECORD;
    }

    /* If end of record, don't transfer any data */
    if (flags & DEV_REOR) {
        chan_flags[chan] &= ~(DEV_WRITE/*|STA_ACTIVE*/);
        if (chan_flags[chan] & DEV_SEL)
            chan_flags[chan] |= DEV_DISCO;
        chan_flags[chan] |= DEV_REOR;
        return TIME_ERROR;
    } else
        chan_flags[chan] |= DEV_WRITE;
    return DATA_OK;
}


void
chan9_set_error(int chan, uint32 mask)
{
    if (chan_flags[chan] & mask)
        return;
    chan_flags[chan] |= mask;
}

t_stat
chan_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
    fprintf(st, "%s\n", chan_description(dptr));
    fprintf(st, "The 7080 supports up to 10 channels. Channel 0 is for unit\n");
    fprintf(st, "record devices.  Channels 1 through 4 are for tape drives.\n\n");
    fprintf (st, "        7261            tapes on Data Synchronizer\n");
    fprintf (st, "        754             Standard 705 tape drives\n\n");
    fprintf (st, "Channels are fixed on the 7080.\n\n");
    fprintf (st, "Channel * is a puesdo channel for unit record devices.\n");

    fprintf(st, "\n");
    fprint_set_help(st, dptr);
    fprint_show_help(st, dptr);
    return SCPE_OK;
}

const char *
chan_description(DEVICE *dptr)
{
    return "IBM 7080 channel controller";
}


