/* i7000_chan.c: IBM 7000 Channel simulator

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

   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;
	        } else {
		    /* 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;
	        }
	    }
	   /* 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;
	    }
	    num_devs[chan] += sim_devices[i]->numunits;
 	    if (dibp->ini != NULL) {
	        for (num = sim_devices[i]->numunits; num > 0; num--) {
		    uptr->flags &= ~UNIT_S_CHAN(017);
	    	    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;
		    } else {
			/* 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;
		    }
		}
	        /* 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;
	        }
		num_devs[chan]++;
		if (dibp->ini != NULL)
		    dibp->ini(uptr, 1);
	    }
	  next:
	    uptr++;
	}
    nextdev:
	;
    }
    return SCPE_OK;
}

/* Sets the device onto a given channel */
t_stat
set_chan(UNIT * uptr, int32 val, char *cptr, void *desc)
{
    DEVICE             *dptr;
    DIB                *dibp;
    int                 newch;
    int                 chan = UNIT_G_CHAN(uptr->flags);
    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;

    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) {
        int	unit;
        for (unit = 0; unit < dptr->numunits; unit++) {
	    /* Set the new channel */
	    dptr->units[unit].flags &= ~UNIT_S_CHAN(017);
	    dptr->units[unit].flags |= UNIT_S_CHAN(newch);
        }
        num_devs[newch] += dptr->numunits;
    } else {
        /* Set the new channel */
        uptr->flags &= ~UNIT_S_CHAN(017);
        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, 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;
	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;
	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, void *desc)
{
    DEVICE             *dptr;
    DIB                *dibp;
    int                 chan = UNIT_G_CHAN(uptr->flags);

    if (uptr == NULL)
	return SCPE_IERR;
    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, 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) {
        int	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, 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);
}

