/* i7000_chan.c: IBM 7000 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

   Common routines for handling channel functions.

*/

#include "i7000_defs.h"

extern DEVICE      *sim_devices[];



int     num_devs[NUM_CHAN];


t_stat
chan_set_devs(DEVICE * dptr)
{
    int                 i;

    for(i = 0; i < NUM_CHAN; i++) {
        num_devs[i] = 0;
    }
    /* Build channel array */
    for (i = 0; sim_devices[i] != NULL; i++) {
        UNIT               *uptr = sim_devices[i]->units;
        DIB                *dibp = (DIB *) sim_devices[i]->ctxt;
        int                 ctype;
        int                 num;

        /* If no DIB, not channel device */
        if (dibp == NULL)
            continue;
        /* Skip channel devices */
        if (sim_devices[i] == &chan_dev)
            continue;
        /* Skip disabled devices */
        if (sim_devices[i]->flags & DEV_DIS)
            continue;
        ctype = dibp->ctype;
        if (dibp->upc > 1) {
            int            chan = UNIT_G_CHAN(uptr->flags);
            int            type = CHAN_G_TYPE(chan_unit[chan].flags);
            if (((1 << type) & ctype) == 0) {
                if ((chan_unit[chan].flags & CHAN_SET) ||
                    ((chan_unit[chan].flags & CHAN_AUTO)
                         && num_devs[chan] != 0)) {
                    for (num = sim_devices[i]->numunits; num > 0; num--)
                        (uptr++)->flags |= UNIT_DIS;
                    goto nextdev;
                }
            }
            /* Set channel to highest type */
            if ((chan_unit[chan].flags & CHAN_SET) == 0) {
                /* Set type to highest found */
                for(type = 7; type >=0; type--)
                   if (ctype & (1 << type))
                     break;
                chan_unit[chan].flags &= ~(CHAN_MODEL);
                chan_unit[chan].flags |= CHAN_S_TYPE(type)|CHAN_SET;
            }
            num_devs[chan] += sim_devices[i]->numunits;
            if (dibp->ini != NULL) {
                for (num = sim_devices[i]->numunits; num > 0; num--) {
                    uptr->flags &= ~UNIT_CHAN;
                    uptr->flags |= UNIT_S_CHAN(chan);
                    dibp->ini(uptr++, 1);
                }
            }
            goto nextdev;
        }

        for (num = sim_devices[i]->numunits; num > 0; num--) {
            int                 chan = UNIT_G_CHAN(uptr->flags);
            int                 type = CHAN_G_TYPE(chan_unit[chan].flags);

            if ((uptr->flags & UNIT_DIS) == 0) {
                if (((1 << type) & ctype) == 0) {
                    if ((chan_unit[chan].flags & CHAN_SET) ||
                        ((chan_unit[chan].flags & CHAN_AUTO)
                         && num_devs[chan] != 0)) {
                        uptr->flags |= UNIT_DIS;
                        goto next;
                    }
                }
                /* Set channel to highest type */
                if ((chan_unit[chan].flags & CHAN_SET) == 0) {
                     /* Set type to highest found */
                     for(type = 7; type >=0; type--)
                        if (ctype & (1 << type))
                          break;
                     chan_unit[chan].flags &= ~(CHAN_MODEL);
                     chan_unit[chan].flags |= CHAN_S_TYPE(type)|CHAN_SET;
                }
                num_devs[chan]++;
                if (dibp->ini != NULL)
                    dibp->ini(uptr, 1);
            }
          next:
            uptr++;
        }
    nextdev:
        ;
    }
    return SCPE_OK;
}

/* Print help for "SET dev CHAN" based on allowed types */
void help_set_chan_type(FILE *st, DEVICE *dptr, const char *name)
{
#if NUM_CHAN > 1
   DIB        *dibp = (DIB *) dptr->ctxt;
   int        ctype = dibp->ctype;
   int        i;
   int        m;

   fprintf (st, "Devices can be moved to any channel via the command\n\n");
   fprintf (st, "   sim> SET %s CHAN=x     where x is", dptr->name);
   if (ctype & 3) {
       if (ctype == 1 || ctype == 2)
          fprintf(st, " only");
       fprintf (st, " %s", chname[0]);
       if ((ctype & ~3) != 0)
          fprintf(st, " or");
   }
   if ((ctype & ~3) != 0)
      fprintf(st, " %s to %s", chname[1], chname[NUM_CHAN-1]);
   fprintf (st, "\n\n%s can be attached to ", name);
   m = 1;
   for(i = 0; ctype != 0; i++) {
      if (ctype & m)  {
         fprintf(st, "%s", chan_type_name[i]);
         ctype &= ~m;
         if (ctype != 0)
            fprintf(st, ", or ");
      }
      m <<= 1;
   }
   fprintf(st, " channel\n");
#endif
}


/* Sets the device onto a given channel */
t_stat
set_chan(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    DEVICE             *dptr;
    DIB                *dibp;
    int                 newch;
    int                 chan;
    int                 num;
    int                 type;
    int                 ctype;
    int                 compat;

    if (cptr == NULL)
        return SCPE_ARG;
    if (uptr == NULL)
        return SCPE_IERR;
    dptr = find_dev_from_unit(uptr);
    if (dptr == NULL)
        return SCPE_IERR;

    chan = UNIT_G_CHAN(uptr->flags);
    if (chan >= NUM_CHAN)
       chan = 0;
    dibp = (DIB *) dptr->ctxt;

    if (dibp == NULL)
        return SCPE_IERR;
    for(newch = 0; newch < NUM_CHAN; newch++)
        if (strcmp(cptr, chname[newch]) == 0)
            break;
    if (newch == NUM_CHAN)
        return SCPE_ARG;
    if (newch == chan)
        return SCPE_OK;

    ctype = dibp->ctype;
    compat = ctype;

    /* Update the number of devices on this channel */
    num_devs[newch] = 0;
    for (num = 0; sim_devices[num] != NULL; num++) {
        UNIT               *u = sim_devices[num]->units;
        DIB                *dibp = (DIB *) sim_devices[num]->ctxt;
        int                 units = sim_devices[num]->numunits;

        /* If no DIB, not channel device */
        if (dibp == NULL)
            continue;
        /* Skip channel devices */
        if (sim_devices[num] == &chan_dev)
            continue;
        /* Skip disabled devices */
        if (sim_devices[num]->flags & DEV_DIS)
            continue;
        if (dibp->upc > 1) {
            if ((u->flags & UNIT_DIS) == 0 &&
                 UNIT_G_CHAN(u->flags) == chan) {
                num_devs[newch] += units;
                compat &= dibp->ctype;
            }
        } else {
             int i;
             for (i = 0; i < units; i++) {
                  if ((u->flags & UNIT_DIS) == 0 &&
                        UNIT_G_CHAN(u->flags) == chan) {
                      num_devs[newch]++;
                      compat &= dibp->ctype;
                  }
                  u++;
            }
        }
    }

    /* If nothing left on channel, drop set bit */
    if (num_devs[newch] == 0 && chan_unit[newch].flags & CHAN_AUTO) {
        chan_unit[newch].flags &= ~CHAN_SET;
        compat = ctype;
    }

    /* Check if same type or everyone can handle new type */
    type = CHAN_G_TYPE(chan_unit[newch].flags);
    if (((1 << type) & ctype) == 0) {
        /* If set or no common types */
        if (chan_unit[newch].flags & CHAN_SET && compat == 0)
            return SCPE_IERR;
        if ((chan_unit[newch].flags & CHAN_AUTO) &&
            (compat == 0 && num_devs[newch] != 0))
            return SCPE_IERR;
        else {
            /* Set type to highest compatable type */
            for(type = 7; type >=0; type--)
                if (compat >> type)
                   break;
            chan_unit[newch].flags &= ~(CHAN_MODEL);
            chan_unit[newch].flags |= CHAN_S_TYPE(type)|CHAN_SET;
        }
    }

    /* Set channel to highest type */
    if ((chan_unit[chan].flags & CHAN_SET) == 0) {
        /* Set type to highest found */
        for(type = 7; type >=0; type--)
            if (ctype >> type)
                break;
        chan_unit[chan].flags &= ~(CHAN_MODEL);
        chan_unit[chan].flags |= CHAN_S_TYPE(type)|CHAN_SET;
    }

   /* Detach unit from orignal channel */
    if (dibp->upc > 1)
        num_devs[chan] -= dptr->numunits;
    else
        num_devs[chan]--;
    if (num_devs[chan] == 0 && (chan_unit[chan].flags & CHAN_AUTO))
        chan_unit[chan].flags &= ~CHAN_SET;

   /* Hook up to new channel */
    if (dibp->upc > 1) {
        uint32  unit;
        for (unit = 0; unit < dptr->numunits; unit++) {
            /* Set the new channel */
            dptr->units[unit].flags &= ~UNIT_CHAN;
            dptr->units[unit].flags |= UNIT_S_CHAN(newch);
        }
        num_devs[newch] += dptr->numunits;
    } else {
        /* Set the new channel */
        uptr->flags &= ~UNIT_CHAN;
        uptr->flags |= UNIT_S_CHAN(newch);
        num_devs[newch]++;
    }
    return SCPE_OK;
}

/* Print devices on channel */
t_stat
print_chan(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    int                 chan = uptr - chan_unit;
    int                 i;

    /* Check all devices */
    fprintf(st, "units=");
    for (i = 0; sim_devices[i] != NULL; i++) {
        UNIT               *u = sim_devices[i]->units;
        DIB                *dibp = (DIB *) sim_devices[i]->ctxt;
        uint32              num;

        /* If no DIB, not channel device */
        if (dibp == NULL)
            continue;
        /* Skip channel devices */
        if (sim_devices[i] == &chan_dev)
            continue;
        /* Skip disabled devices */
        if (sim_devices[i]->flags & DEV_DIS)
            continue;
        if (dibp->upc > 1) {
            if ((u->flags & UNIT_DIS) == 0 &&
                 UNIT_G_CHAN(u->flags) == chan)
                 fprintf(st, "%s, ", sim_devices[i]->name);
        } else {
             for (num = 0; num < sim_devices[i]->numunits; num++) {
                  if ((u->flags & UNIT_DIS) == 0 &&
                        UNIT_G_CHAN(u->flags) == chan)
                      fprintf(st, "%s%d, ", sim_devices[i]->name, num);
                  u++;
            }
        }
    }
    return SCPE_OK;
}

t_stat
get_chan(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    DEVICE             *dptr;
    DIB                *dibp;
    int                 chan;

    if (uptr == NULL)
        return SCPE_IERR;
    chan = UNIT_G_CHAN(uptr->flags);
    dptr = find_dev_from_unit(uptr);
    if (dptr == NULL)
        return SCPE_IERR;
    dibp = (DIB *) dptr->ctxt;
    if (dibp == NULL)
        return SCPE_IERR;
    fprintf(st, "Chan=%s", chname[chan]);
    return SCPE_OK;
}

t_stat
chan9_set_select(UNIT * uptr, int32 val, CONST char *cptr, void *desc)
{
    int                 newsel;
    DEVICE             *dptr;
    DIB                *dibp;

    if (cptr == NULL)
        return SCPE_ARG;
    if (uptr == NULL)
        return SCPE_IERR;
    if (*cptr == '\0' || cptr[1] != '\0')
        return SCPE_ARG;
    if (*cptr == '0')
        newsel = 0;
    else if (*cptr == '1')
        newsel = 1;
    else
        return SCPE_ARG;
    dptr = find_dev_from_unit(uptr);
    if (dptr == NULL)
        return SCPE_IERR;

    dibp = (DIB *) dptr->ctxt;

    if (dibp == NULL)
        return SCPE_IERR;

   /* Change to new selection. */
    if (dibp->upc > 1) {
        uint32  unit;
        for (unit = 0; unit < dptr->numunits; unit++) {
            if (newsel)
                dptr->units[unit].flags |= UNIT_SELECT;
            else
                dptr->units[unit].flags &= ~UNIT_SELECT;
        }
    } else {
        if (newsel)
            uptr->flags |= UNIT_SELECT;
        else
            uptr->flags &= ~UNIT_SELECT;
    }
    return SCPE_OK;
}

t_stat
chan9_get_select(FILE * st, UNIT * uptr, int32 v, CONST void *desc)
{
    if (uptr == NULL)
        return SCPE_IERR;
    if (uptr->flags & UNIT_SELECT)
        fputs("Select=1", st);
    else
        fputs("Select=0", st);
    return SCPE_OK;
}

/* Check channel for error */
int chan_error(int chan)
{
    return chan_flags[chan] & CHS_ATTN;
}

/* Check channel for flag, clear it if it was set */
int chan_stat(int chan, uint32 flag)
{
    if (chan_flags[chan] & flag) {
        chan_flags[chan] &= ~flag;
        return 1;
    }
    return 0;
}

/* Check channel for flag */
int chan_test(int chan, uint32 flag)
{
    if (chan_flags[chan] & flag)
        return 1;
    return 0;
}

/* Check channel is selected */
int chan_select(int chan)
{
    return chan_flags[chan] & DEV_SEL;
}

/* Check channel is active */
int chan_active(int chan)
{
    return (chan_flags[chan] &
                (DEV_DISCO |DEV_SEL | STA_ACTIVE | STA_WAIT | STA_TWAIT)) != 0;
}

void
chan_set_attn(int chan)
{
    chan_flags[chan] |= CHS_ATTN;
}

void
chan_set_eof(int chan)
{
    chan_flags[chan] |= CHS_EOF;
}

void
chan_set_error(int chan)
{
    chan_flags[chan] |= CHS_ERR;
}

void
chan_set_sel(int chan, int need)
{
    chan_flags[chan] &=
        ~(DEV_WEOR | DEV_REOR | DEV_FULL | DEV_WRITE | DEV_DISCO);
    chan_flags[chan] |= DEV_SEL;
    if (need)
        chan_flags[chan] |= DEV_WRITE;
}

void
chan_clear_status(int chan)
{
    chan_flags[chan] &=
        ~(CHS_ATTN | CHS_EOT | CHS_BOT | DEV_REOR | DEV_WEOR);
}

void
chan_set(int chan, uint32 flag)
{
    chan_flags[chan] |= flag;
}

void
chan_clear(int chan, uint32 flag)
{
    chan_flags[chan] &= ~flag;
}

void
chan9_clear_error(int chan, int sel) {
    chan_flags[chan] &= ~(SNS_UEND | (SNS_ATTN1 >> sel));
}

void
chan9_set_attn(int chan, int sel)
{
    uint16              mask = SNS_ATTN1 >> sel;

    chan9_set_error(chan, mask);
}

