/* i7000_urec.c: IBM 7000 Unit record devices.

   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.

   This is the standard card reader.
   This is the standard card punch.
   This is the standard line printer.
   This is the standard inquiry or console interface.

   These units each buffer one record in local memory and signal
   ready when the buffer is full or empty. The channel must be
   ready to recieve/transmit data when they are activated since
   they will transfer their block during chan_cmd. All data is
   transmitted as BCD characters.

*/

#include "i7000_defs.h"
#include "card.h"
#include "sim_defs.h"

#define UNIT_CDR	UNIT_ATTABLE | UNIT_RO | UNIT_DISABLE | UNIT_ROABLE
#define UNIT_CDP	UNIT_ATTABLE | UNIT_DISABLE
#define UNIT_LPR	UNIT_ATTABLE | UNIT_DISABLE


/* Flags for punch and reader. */
#define ECHO		(1 << (UNIT_V_UF+2))
#define ATTENA		(1 << (UNIT_V_UF+3))
#define ATTENB		(1 << (UNIT_V_UF+4))

/* std devices. data structures

   cdr_dev	Card Reader device descriptor
   cdr_unit	Card Reader unit descriptor
   cdr_reg	Card Reader register list
   cdr_mod	Card Reader modifiers list
*/

/* Device status information stored in u5 */
#define URCSTA_CARD	0004	/* Unit has card in buffer */
#define URCSTA_FULL	0004	/* Unit has full buffer */
#define URCSTA_BUSY	0010	/* Device is busy */
#define URCSTA_WDISCO	0020	/* Device is wait for disconnect */
#define URCSTA_READ	0040	/* Device is reading channel */
#define URCSTA_WRITE	0100	/* Device is reading channel */
#define URCSTA_INPUT	0200	/* Console fill buffer from keyboard */
#define URCSTA_WMKS	0400	/* Printer print WM as 1 */
#define URCSTA_SKIPAFT	01000	/* Skip to line after printing next line */
#define URCSTA_NOXFER	01000	/* Don't set up to transfer after feed */

extern int32        sim_interval;
extern void	    chan_clear_attn_inq(int chan);
extern void	    chan_set_attn_inq(int chan);
#ifdef I7070
extern void         chan_set_attn_a(int chan);
extern void         chan_set_attn_b(int chan);
#endif
extern uint8	    lpr_chan9[NUM_CHAN];
#ifdef I7010
extern void         chan_set_attn_urec(int chan, uint16 addr);
extern uint8	    lpr_chan12[NUM_CHAN];
#endif

#ifdef NUM_DEVS_CDR
uint32              cdr_cmd(UNIT *, uint16, uint16);
t_stat              cdr_boot(int32, DEVICE *);
t_stat              cdr_srv(UNIT *);
t_stat              cdr_reset(DEVICE *);
t_stat              cdr_attach(UNIT *, char *);
t_stat              cdr_detach(UNIT *);
extern t_stat       chan_boot(int32, DEVICE *);
#endif

#ifdef NUM_DEVS_CDP
uint32              cdp_cmd(UNIT *, uint16, uint16);
void                cdp_ini(UNIT *, t_bool);
t_stat              cdp_srv(UNIT *);
t_stat              cdp_reset(DEVICE *);
t_stat              cdp_attach(UNIT *, char *);
t_stat              cdp_detach(UNIT *);
#endif

#ifdef NUM_DEVS_LPR 
struct _lpr_data
{
    uint8               lbuff[145];	/* Output line buffer */
}
lpr_data[NUM_DEVS_LPR];

uint32              lpr_cmd(UNIT *, uint16, uint16);
void                lpr_ini(UNIT *, t_bool);
t_stat              lpr_srv(UNIT *);
t_stat              lpr_reset(DEVICE *);
t_stat              lpr_attach(UNIT *, char *);
t_stat              lpr_detach(UNIT *);
t_stat		    lpr_setlpp(UNIT *, int32, char *, void *);
t_stat		    lpr_getlpp(FILE *, UNIT *, int32, void *);
#endif

#ifdef NUM_DEVS_CON 
struct _con_data
{
    uint8               ibuff[145];	/* Input line buffer */
    uint8		inptr;
}
con_data[NUM_DEVS_CON];

uint32              con_cmd(UNIT *, uint16, uint16);
void                con_ini(UNIT *, t_bool);
t_stat              con_srv(UNIT *);
t_stat              con_reset(DEVICE *);
t_stat              con_attach(UNIT *, char *);
t_stat              con_detach(UNIT *);
#endif

extern char         ascii_to_six[128];
extern char         mem_to_ascii[64];
extern t_stat       chan_boot(int32, DEVICE *);
extern char         six_to_ascii[64];


#ifdef NUM_DEVS_CDR
UNIT                cdr_unit[] = {
   {UDATA(cdr_srv, UNIT_S_CHAN(CHAN_CHUREC) | UNIT_CDR, 0), 300},	/* A */
#if NUM_DEVS_CDR > 1
   {UDATA(cdr_srv, UNIT_S_CHAN(CHAN_CHUREC+1) | UNIT_CDR, 0), 300},	/* B */
#endif
};

MTAB                cdr_mod[] = {
    {MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT",
               &card_set_fmt, &card_show_fmt, NULL},    
#ifdef I7070
    {ATTENA|ATTENB, 0, NULL, "NOATTEN", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENA, "ATTENA", "ATTENA", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENB, "ATTENB", "ATTENB", NULL, NULL, NULL},
#endif
#ifdef I7010
    {MTAB_XTD | MTAB_VUN | MTAB_VAL, 0, "CHAN", "CHAN", &set_chan,
        &get_chan, NULL},
#endif   
    {0}
};

DEVICE              cdr_dev = {
    "CR", cdr_unit, NULL, cdr_mod,
    NUM_DEVS_CDR, 8, 15, 1, 8, 8,
    NULL, NULL, NULL, &cdr_boot, &cdr_attach, &card_detach,
    &cdr_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug
};
#endif

#ifdef NUM_DEVS_CDP
UNIT                cdp_unit[] = {
    {UDATA(cdp_srv, UNIT_S_CHAN(CHAN_CHUREC) | UNIT_CDP, 0), 600},	/* A */
#if NUM_DEVS_CDP > 1
    {UDATA(cdp_srv, UNIT_S_CHAN(CHAN_CHUREC+1) | UNIT_CDP, 0), 600},	/* B */
#endif
};

MTAB                cdp_mod[] = {
    {MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT",
               &card_set_fmt, &card_show_fmt, NULL},    
#ifdef I7070
    {ATTENA|ATTENB, 0, NULL, "NOATTEN", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENA, "ATTENA", "ATTENA", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENB, "ATTENB", "ATTENB", NULL, NULL, NULL},
#endif
#ifdef I7010
    {MTAB_XTD | MTAB_VUN | MTAB_VAL, 0, "CHAN", "CHAN", &set_chan,
        &get_chan, NULL},
#endif   
    {0}
};

DEVICE              cdp_dev = {
    "CP", cdp_unit, NULL, cdp_mod,
    NUM_DEVS_CDP, 8, 15, 1, 8, 8,
    NULL, NULL, NULL, NULL, &cdp_attach, &cdp_detach,
    &cdp_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug
};

#ifdef STACK_DEV
UNIT stack_unit[] = {
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_SEQ+UNIT_ATTABLE, 0) },
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    { UDATA (NULL, UNIT_DIS, 0) },                      /* unused */
    };

DEVICE stack_dev = {
    "STKR", stack_unit, NULL, NULL,
    NUM_DEVS_CDP * 10, 10, 31, 1, 8, 7,
    NULL, NULL, NULL,
    NULL, NULL, NULL
    };
#endif
#endif

#ifdef NUM_DEVS_LPR
UNIT                lpr_unit[] = {
    {UDATA(lpr_srv, UNIT_S_CHAN(CHAN_CHUREC) | UNIT_LPR, 55), 300},	/* A */
#if NUM_DEVS_LPR > 1
    {UDATA(lpr_srv, UNIT_S_CHAN(CHAN_CHUREC+1) | UNIT_LPR, 55), 300},	/* B */
#endif
};

MTAB                lpr_mod[] = {
    {ECHO, 0,     NULL, "NOECHO", NULL, NULL, NULL},
    {ECHO, ECHO, "ECHO", "ECHO", NULL, NULL, NULL},
    {MTAB_XTD|MTAB_VUN|MTAB_VAL, 0, "LINESPERPAGE", "LINESPERPAGE",
	&lpr_setlpp, &lpr_getlpp, NULL},
#ifdef I7070
    {ATTENA|ATTENB, 0, NULL, "NOATTEN", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENA, "ATTENA", "ATTENA", NULL, NULL, NULL},
    {ATTENA|ATTENB, ATTENB, "ATTENB", "ATTENB", NULL, NULL, NULL},
#endif
#ifdef I7010
    {MTAB_XTD | MTAB_VUN | MTAB_VAL, 0, "CHAN", "CHAN", &set_chan,
        &get_chan, NULL},
#endif   
    {0}
};

DEVICE              lpr_dev = {
    "LP", lpr_unit, NULL, lpr_mod,
    NUM_DEVS_LPR, 8, 15, 1, 8, 8,
    NULL, NULL, NULL, NULL, &lpr_attach, &lpr_detach,
    &lpr_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug
};
#endif

#ifdef NUM_DEVS_CON
UNIT                con_unit[] = {
    {UDATA(con_srv, UNIT_S_CHAN(CHAN_CHUREC), 0), 0},	/* A */
};

DEVICE              con_dev = {
    "CON", con_unit, NULL, NULL,
    NUM_DEVS_LPR, 8, 15, 1, 8, 8,
    NULL, NULL, NULL, NULL, NULL, NULL,
    &con_dib, DEV_DISABLE | DEV_DEBUG, 0, dev_debug
};
#endif



#ifdef NUM_DEVS_CDR
/*
 * Device entry points for card reader.
 */
uint32 cdr_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - cdr_unit);
    int			stk = dev & 017;

    /* Are we currently tranfering? */
    if (uptr->u5 & URCSTA_READ)
	return SCPE_BUSY;

    /* Test ready */
    if (cmd == IO_TRS && uptr->flags & UNIT_ATT) {
        sim_debug(DEBUG_CMD, &cdr_dev, "%d: Test Rdy\n", u);
	return SCPE_OK;
    }

    if (stk == 10)
        stk = 0;

#ifdef STACK_DEV
    uptr->u5 &= ~0xF0000;
    uptr->u5 |= stk << 16;
#endif
    /* Process commands */
    switch(cmd) {
    case IO_RDS:
	sim_debug(DEBUG_CMD, &cdr_dev, "%d: Cmd RDS %02o\n", u, dev & 077);
#ifdef I7010
	if (stk!= 9) 
#endif
	uptr->u5 &= ~(URCSTA_CARD|URCSTA_ERR);
	break;
    case IO_CTL:
	sim_debug(DEBUG_CMD, &cdr_dev, "%d: Cmd CTL %02o\n", u, dev & 077);
#ifdef I7010
	uptr->u5 |= URCSTA_NOXFER;
#endif
	break;
    default:
        chan_set_attn(chan);
        return SCPE_IOERR;
    }

    /* If at eof, just return EOF */
    if (uptr->u5 & URCSTA_EOF) {
	chan_set_eof(chan);
        chan_set_attn(chan);
	return SCPE_OK;
    }

    uptr->u5 |= URCSTA_READ;
    uptr->u4 = 0;

    if ((uptr->u5 & URCSTA_NOXFER) == 0)
        chan_set_sel(chan, 0);
    /* Wake it up if not busy */
    if ((uptr->u5 & URCSTA_BUSY) == 0) 
        sim_activate(uptr, 50);
    return SCPE_OK;
}

/* Handle transfer of data for card reader */
t_stat
cdr_srv(UNIT *uptr) {
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - cdr_unit);

    /* Waiting for disconnect */
    if (uptr->u5 & URCSTA_WDISCO) {
	if (chan_stat(chan, DEV_DISCO)) {
	    chan_clear(chan, DEV_SEL|DEV_WEOR);
	    uptr->u5 &= ~ URCSTA_WDISCO;
	} else {
	    /* No disco yet, try again in a bit */
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	}
        /* If still busy, schedule another wait */
        if (uptr->u5 & URCSTA_BUSY)
             sim_activate(uptr, uptr->wait);
    }

    if (uptr->u5 & URCSTA_BUSY) {
        uptr->u5 &= ~URCSTA_BUSY;
#ifdef I7070
        switch(uptr->flags & (ATTENA|ATTENB)) {
        case ATTENA: chan_set_attn_a(chan); break;
        case ATTENB: chan_set_attn_b(chan); break;
        }
#endif
    }

    /* Check if new card requested. */
    if (uptr->u4 == 0 && uptr->u5 & URCSTA_READ &&
		(uptr->u5 & URCSTA_CARD) == 0) {
	switch(read_card(uptr)) {
	case SCPE_EOF:
	case SCPE_UNATT:
	     chan_set_eof(chan);
	     chan_set_attn(chan);
             chan_clear(chan, DEV_SEL);
	     uptr->u5 &= ~URCSTA_BUSY;
	     return SCPE_OK;
	case SCPE_IOERR:
	     uptr->u5 |= URCSTA_ERR;
	     uptr->u5 &= ~URCSTA_BUSY;
             chan_set_attn(chan);
             chan_clear(chan, DEV_SEL);
             return SCPE_OK;
	case SCPE_OK:	
	     uptr->u5 |= URCSTA_CARD;
#ifdef I7010
 	     chan_set_attn_urec(chan, cdr_dib.addr);
#endif
	     break;
	}
    }

    if (uptr->u5 & URCSTA_NOXFER) {
	uptr->u5 &= ~(URCSTA_NOXFER|URCSTA_READ);
	return SCPE_OK;
    }

#if 0
    if (uptr->u5 & URCSTA_READ && uptr->u4 == 80) {
	uint8	ch	= 077;
        switch(chan_write_char(chan, &ch, DEV_REOR)) {
        case TIME_ERROR:
        case END_RECORD:
	    uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY;
	    uptr->u5 &= ~URCSTA_READ;
	    break;
        case DATA_OK:
	    uptr->u4++;
            break;
        }
	sim_debug(DEBUG_DATA, &cdr_dev, "%d: Char > %02o\n", u, ch);
        sim_activate(uptr, 10);
    }
#endif
    /* Copy next column over */
    if (uptr->u5 & URCSTA_READ && uptr->u4 < 80) {
        struct _card_data   *data;
        uint8                ch = 0;

        data = (struct _card_data *)uptr->u3;
        ch = hol_to_bcd(data->image[uptr->u4]);

        if (ch == 0x7f) {
	     uptr->u5 |= URCSTA_ERR;
	     ch = 017;
        }
//#ifdef I7080
//	/* Switch Punch Group mark to internal group mark */
//	if (ch == 075)
//	     ch = 077;
//#endif
//        switch(chan_write_char(chan, &ch, 0)) {
        switch(chan_write_char(chan, &ch, (uptr->u4 == 79)? DEV_REOR: 0)) {
        case TIME_ERROR:
        case END_RECORD:
	    uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY;
	    uptr->u5 &= ~URCSTA_READ;
	    break;
        case DATA_OK:
	    uptr->u4++;
            break;
        }
	sim_debug(DEBUG_DATA, &cdr_dev, "%d: Char > %02o\n", u, ch);
        sim_activate(uptr, 10);
    }
    return SCPE_OK;
}

/* Boot from given device */
t_stat
cdr_boot(int32 unit_num, DEVICE * dptr)
{
    UNIT               *uptr = &dptr->units[unit_num];
    t_stat              r;

    if ((uptr->flags & UNIT_ATT) == 0)
	return SCPE_UNATT;	/* attached? */
    /* Read in one record */
    r = cdr_cmd(uptr, IO_RDS, cdr_dib.addr);
    if (r != SCPE_OK)
	return r;
    r = chan_boot(unit_num, dptr);
    return r;
}

t_stat
cdr_attach(UNIT * uptr, char *file)
{
    t_stat              r;

    if ((r = card_attach(uptr, file)) != SCPE_OK)
	return r;
    uptr->u5 &= URCSTA_BUSY|URCSTA_WDISCO;
    uptr->u4 = 0;
    uptr->u6 = 0;
#ifdef I7010
    chan_set_attn_urec(UNIT_G_CHAN(uptr->flags), cdr_dib.addr);
#endif
    return SCPE_OK;
}

#endif


#ifdef NUM_DEVS_CDP
/* Card punch routine

   Modifiers have been checked by the caller
   C modifier is recognized (column binary is implemented)
*/


uint32 cdp_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - cdp_unit);
    int			stk = dev & 017;

    /* Are we currently tranfering? */
    if (uptr->u5 & URCSTA_WRITE)
	return SCPE_BUSY;

    if (stk == 10)
        stk = 0;
    if ((uptr->flags & UNIT_ATT) == 0) {
#ifdef STACK_DEV
	if ((stack_unit[stk * u].flags & UNIT_ATT) == 0)
#endif
	return SCPE_IOERR;
    }

    switch(cmd) {
    /* Test ready */
    case IO_TRS:
	sim_debug(DEBUG_CMD, &cdp_dev, "%d: Cmd TRS\n", u);
	return SCPE_OK;

    /* Suppress punch */
    case IO_RUN:
	uptr->u5 &= ~URCSTA_FULL;
	sim_debug(DEBUG_CMD, &cdp_dev, "%d: Cmd RUN\n", u);
	return SCPE_OK;

    /* Retieve data from CPU */
    case IO_WRS:
#ifdef STACK_DEV
	uptr->u5 &= ~0xF0000;
	uptr->u5 |= stk << 16;
#endif
	sim_debug(DEBUG_CMD, &cdp_dev, "%d: Cmd WRS\n", u);
	chan_set_sel(chan, 1);
	uptr->u5 |= URCSTA_WRITE;
	uptr->u4 = 0;
	if ((uptr->u5 & URCSTA_BUSY) == 0)
	    sim_activate(uptr, 50);
	return SCPE_OK;
    }
    chan_set_attn(chan);
    return SCPE_IOERR;
}

/* Handle transfer of data for card punch */
t_stat
cdp_srv(UNIT *uptr) {
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - cdp_unit);
    /* Waiting for disconnect */
    if (uptr->u5 & URCSTA_WDISCO) {
	if (chan_stat(chan, DEV_DISCO)) {
	    chan_clear(chan, DEV_SEL|DEV_WEOR);
	    uptr->u5 &= ~ URCSTA_WDISCO;
	} else {
	    /* No disco yet, try again in a bit */
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	}
        /* If still busy, schedule another wait */
        if (uptr->u5 & URCSTA_BUSY)
             sim_activate(uptr, uptr->wait);
    }

    if (uptr->u5 & URCSTA_BUSY) {
	/* Done waiting, punch card */
	if (uptr->u5 & URCSTA_FULL) {
#ifdef STACK_DEV
	      switch(punch_card(uptr, 
			&stack_unit[(u * 10) + ((uptr->u5 >> 16) & 0xf)])) {
#else
	      switch(punch_card(uptr, NULL)) {
#endif
	      case SCPE_EOF:
	      case SCPE_UNATT:
	          chan_set_eof(chan);
	          break;
	         /* If we get here, something is wrong */
	      case SCPE_IOERR:
	          chan_set_error(chan);
	          break;
	      case SCPE_OK:	
	          break;
	      }
	      uptr->u5 &= ~URCSTA_FULL;
	}
        uptr->u5 &= ~URCSTA_BUSY;
#ifdef I7070
        switch(uptr->flags & (ATTENA|ATTENB)) {
        case ATTENA: chan_set_attn_a(chan); break;
        case ATTENB: chan_set_attn_b(chan); break;
        }
#endif
#ifdef I7010
	chan_set_attn_urec(chan, cdp_dib.addr);
#endif
    }

    /* Copy next column over */
    if (uptr->u5 & URCSTA_WRITE && uptr->u4 < 80) {
        struct _card_data   *data;
        uint8               ch = 0;

        data = (struct _card_data *)uptr->u3;

#ifdef I7080
	switch(chan_read_char(chan, &ch, 0)) {
#else
	switch(chan_read_char(chan, &ch,
				(uptr->u4 == 79)? DEV_REOR: 0)) {
#endif
	case TIME_ERROR:
	case END_RECORD:
	     uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY|URCSTA_FULL;
	     uptr->u5 &= ~URCSTA_WRITE;
	case DATA_OK:
	    sim_debug(DEBUG_DATA, &cdp_dev, "%d: Char < %02o\n", u, ch);
            data->image[uptr->u4++] = bcd_to_hol(ch);
	    break;
	}
        sim_activate(uptr, 10);
    }
    return SCPE_OK;
}


void
cdp_ini(UNIT *uptr, t_bool f) {
}

t_stat
cdp_attach(UNIT * uptr, char *file)
{
    t_stat              r;

    if ((r = card_attach(uptr, file)) != SCPE_OK)
	return r;
    uptr->u5 = 0;
    return SCPE_OK;
}

t_stat
cdp_detach(UNIT * uptr)
{
    if (uptr->u5 & URCSTA_FULL) 
#ifdef STACK_DEV
	punch_card(uptr, &stack_unit[
		((uptr - cdp_unit) * 10) + ((uptr->u5 >> 16) & 0xf)]);
#else
	punch_card(uptr, NULL);
#endif
    return card_detach(uptr);
}
#endif


/* Line printer routines
*/

#ifdef NUM_DEVS_LPR
t_stat
lpr_setlpp(UNIT *uptr, int32 val, char *cptr, void *desc) 
{
    int i;
    if (cptr == NULL)
	return SCPE_ARG;
    if (uptr == NULL)
	return SCPE_IERR;
    i = 0;
    while(*cptr != '\0') {
	if (*cptr < '0' || *cptr > '9')
	   return SCPE_ARG;
	i = (i * 10) + (*cptr++) - '0';
    }
    if (i < 20 || i > 100)
	return SCPE_ARG;
    uptr->capac = i;
    uptr->u4 = 0;
    return SCPE_OK;
}

t_stat
lpr_getlpp(FILE *st, UNIT *uptr, int32 v, void *desc)
{
    if (uptr == NULL)
	return SCPE_IERR;
    fprintf(st, "linesperpage=%d", uptr->capac);
    return SCPE_OK;
}

t_stat
print_line(UNIT * uptr, int chan, int unit)
{
/* Convert word record into column image */
/* Check output type, if auto or text, try and convert record to bcd first */
/* If failed and text report error and dump what we have */
/* Else if binary or not convertable, dump as image */

    char                out[150];	/* Temp conversion buffer */
    int                 i;

    if ((uptr->flags & (UNIT_ATT | ECHO)) == 0)
	return SCPE_UNATT;	/* attached? */

    /* Try to convert to text */
    memset(out, 0, sizeof(out));

    /* Scan each column */
    for (i = 0; i < 144; i++) {
	int                 bcd = lpr_data[unit].lbuff[i] & 077;

	out[i] = six_to_ascii[bcd];
    }

    /* Trim trailing spaces */
    for (--i; i > 0 && out[i] == ' '; i--) ;
    out[++i] = '\n';
    out[++i] = '\0';

    /* Print out buffer */
    if (uptr->flags & UNIT_ATT)
	sim_fwrite(&out, 1, i, uptr->fileref);
    if (uptr->flags & ECHO) {
	int                 j = 0;

	while (j <= i)
	    sim_putchar(out[j++]);
    }
    uptr->u4++;
    if (uptr->u4 > uptr->capac) {
	uptr->u4 = 1;
    }
 
    if (uptr->u5 & URCSTA_SKIPAFT) {
	i = (uptr->u5 >> 12) & 0x7f;
	if (i == 0) {
    	    if (uptr->flags & UNIT_ATT)
		sim_fwrite("\r", 1, 1, uptr->fileref);
	    if (uptr->flags & ECHO) 
	        sim_putchar('\r');
	} else { 
	    for (; i > 1; i--) {
    	        if (uptr->flags & UNIT_ATT)
		    sim_fwrite("\n", 1, 1, uptr->fileref);
	        if (uptr->flags & ECHO) 
	            sim_putchar('\n');
	        uptr->u4++;
                if (uptr->u4 > uptr->capac) {
	            uptr->u4 = 1;
                }
            }
	}
	uptr->u5 &= ~(URCSTA_SKIPAFT|(0x7f << 12));
    }

    if (uptr->u4 == 1)
	lpr_chan9[chan] = 1;
#ifdef I7010
    if (uptr->u4 == uptr->capac)
	lpr_chan12[chan] = 1;
#endif

    return SCPE_OK;
}


uint32 lpr_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - lpr_unit);
#ifdef I7010
    int                 i;
#endif

    /* Are we currently tranfering? */
    if (uptr->u5 & URCSTA_WRITE)
	return SCPE_BUSY;

    switch(cmd) {
        /* Test ready */
    case IO_TRS:
	if (uptr->flags & UNIT_ATT) 
	    return SCPE_OK;
	break;

    /* Suppress punch */
    case IO_RUN:
	sim_debug(DEBUG_CMD, &lpr_dev, "%d: Cmd RUN\n", u);
	uptr->u5 &= ~URCSTA_FULL;
	return SCPE_OK;

    /* Get record from CPU */
    case IO_WRS:
	sim_debug(DEBUG_CMD, &lpr_dev, "%d: Cmd WRS\n", u);
	lpr_chan9[chan] = 0;
#ifdef I7010
	lpr_chan12[chan] = 0;
	switch (dev & 017) {
	case 01:
		uptr->u5 |= URCSTA_WMKS;
		break;
	case 012:
		uptr->u5 &= ~URCSTA_WMKS;
		break;
	default:
		return SCPE_IOERR;
	}
#endif
	chan_set_sel(chan, 1);
	uptr->u5 |= URCSTA_WRITE;
	uptr->u3 = 0;
	if ((uptr->u5 & URCSTA_BUSY) == 0) 
	    sim_activate(uptr, 50);
	return SCPE_OK;

    case IO_CTL:
	sim_debug(DEBUG_CMD, &lpr_dev, "%d: Cmd CTL %02o\n", u, dev & 077);
#ifdef I7010
	/*    1-0 immediate skip to channel */
	/*    00xxxx	skip to channel immediate */
	/*    11xxxx	skip to channel after */
	/*    1000xx	space before */
	/*    0100xx    space after */
	switch(dev & 060) {
        case 020: /* Space after */
	     uptr->u5 |= URCSTA_SKIPAFT | ((dev & 03) << 12);
	     break;
	case 040: /* Space before */
	     for (i = dev & 03; i > 1; i--) {
    		if (uptr->flags & UNIT_ATT)
		    sim_fwrite("\n", 1, 1, uptr->fileref);
		if (uptr->flags & ECHO) {
	            sim_putchar('\r');
	            sim_putchar('\n');
		}
             }
	     break;
	case 0:	  /* Skip channel immediate */
        case 060: /* Skip channel after */
	     i = 0;
	     switch(dev & 017) {
	     case 3:	i = 5 - (uptr->u4 % 5); break;
	     case 2:	i = 8 - (uptr->u4 % 8); break;
	     case 1:
	     case 9:	if (uptr->u4 == 1)
			    break;
			i = uptr->capac - uptr->u4 + 1; break;
	     case 12:	i = (uptr->capac/2) - uptr->u4; break;
             }
	     if (i == 0)
		break;
	     if (dev & 060) {
	        uptr->u5 |= URCSTA_SKIPAFT | (i << 12);
		break;
	     }
	     for (; i > 0; i--) {
    		if (uptr->flags & UNIT_ATT)
		    sim_fwrite("\n", 1, 1, uptr->fileref);
		if (uptr->flags & ECHO) {
	            sim_putchar('\r');
	            sim_putchar('\n');
		}
             }
	     break;
	}
        if (uptr->u4 == uptr->capac)
	    lpr_chan12[chan] = 1;
#endif
        if (uptr->u4 == 1)
	    lpr_chan9[chan] = 1;
	return SCPE_OK;
    }
    chan_set_attn(chan);
    return SCPE_IOERR;
}

/* Handle transfer of data for printer */
t_stat
lpr_srv(UNIT *uptr) {
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - lpr_unit);
    /* Waiting for disconnect */
    if (uptr->u5 & URCSTA_WDISCO) {
	if (chan_stat(chan, DEV_DISCO)) {
	    chan_clear(chan, DEV_SEL|DEV_WEOR);
	    uptr->u5 &= ~ URCSTA_WDISCO;
	} else {
	    /* No disco yet, try again in a bit */
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	}
        /* If still busy, schedule another wait */
        if (uptr->u5 & URCSTA_BUSY)
             sim_activate(uptr, uptr->wait);
    }

    if (uptr->u5 & URCSTA_BUSY) {
	/* Done waiting, print line */
	if (uptr->u5 & URCSTA_FULL) {
	      uptr->u5 &= ~URCSTA_FULL;
	      switch(print_line(uptr, chan, u)) {
	      case SCPE_EOF:
	      case SCPE_UNATT:
	          chan_set_eof(chan);
	          break;
	         /* If we get here, something is wrong */
	      case SCPE_IOERR:
	          chan_set_error(chan);
	          break;
	      case SCPE_OK:	
	          break;
	      }
	}
	memset(&lpr_data[u].lbuff[0], 0, 144);
        uptr->u5 &= ~URCSTA_BUSY;
#ifdef I7070
        switch(uptr->flags & (ATTENA|ATTENB)) {
        case ATTENA: chan_set_attn_a(chan); break;
        case ATTENB: chan_set_attn_b(chan); break;
        }
#endif
#ifdef I7010
	chan_set_attn_urec(chan, lpr_dib.addr);
#endif
    }

    /* Copy next column over */
    if (uptr->u5 & URCSTA_WRITE && uptr->u3 < 144) {
	switch(chan_read_char(chan, &lpr_data[u].lbuff[uptr->u3],
		(uptr->u3 == 143)?DEV_REOR: 0)) {
	case TIME_ERROR:
	case END_RECORD:
	    uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY|URCSTA_FULL;
	    uptr->u5 &= ~URCSTA_WRITE;
	case DATA_OK:
	    sim_debug(DEBUG_DATA, &lpr_dev, "%d: Char < %02o\n", u, 
			lpr_data[u].lbuff[uptr->u3]);
#ifdef I7010
	    if (uptr->u5 & URCSTA_WMKS) {
		if (lpr_data[u].lbuff[uptr->u3] & 0200)	
		    lpr_data[u].lbuff[uptr->u3] = 1;
		else
		    lpr_data[u].lbuff[uptr->u3] = 012;
	    }
#endif
	    uptr->u3++;
	    break;
	}
        sim_activate(uptr, 10);
    }
    return SCPE_OK;
}

void
lpr_ini(UNIT *uptr, t_bool f) {
}

t_stat
lpr_attach(UNIT * uptr, char *file)
{
    t_stat              r;

    if ((r = attach_unit(uptr, file)) != SCPE_OK)
	return r;
    uptr->u5 = 0;
    uptr->u4 = 0;
    return SCPE_OK;
}

t_stat
lpr_detach(UNIT * uptr)
{
    if (uptr->u5 & URCSTA_FULL) 
	print_line(uptr, UNIT_G_CHAN(uptr->flags), uptr - cdp_unit);
    return detach_unit(uptr);
}

#endif

/* Console printer routines.
*/

#ifdef NUM_DEVS_CON

void 
con_ini(UNIT *uptr, t_bool f) {
     int                 u = (uptr - con_unit);
     con_data[u].inptr = 0;
     uptr->u5 = 0;
     sim_activate(uptr, 1000);
}

uint32
con_cmd(UNIT * uptr, uint16 cmd, uint16 dev)
{
    int                 chan = UNIT_G_CHAN(uptr->flags);
    int                 u = (uptr - con_unit);

    /* Are we currently tranfering? */
    if (uptr->u5 & (URCSTA_READ|URCSTA_WRITE|URCSTA_BUSY))
	return SCPE_BUSY;
    
    switch (cmd) {
    /* Test ready */
    case IO_TRS:
	sim_debug(DEBUG_CMD, &con_dev, "%d: Cmd TRS\n", u);
	return SCPE_OK;

    /* Get record from CPU */
    case IO_WRS:
	sim_putchar('R');
	sim_putchar(' ');
	sim_debug(DEBUG_CMD, &con_dev, "%d: Cmd WRS\n", u);
	chan_set_sel(chan, 1);
	uptr->u5 |= URCSTA_WRITE;
	uptr->u3 = 0;
	return SCPE_OK;

    /* Send record to CPU */
    case IO_RDS:
	if (uptr->u5 & URCSTA_INPUT)
	    return SCPE_BUSY;
	if (con_data[u].inptr == 0) {
	    /* Activate input so we can get response */
	    uptr->u5 |= URCSTA_INPUT;
	    sim_putchar('I');
	    sim_putchar(' ');
	}	
	sim_debug(DEBUG_CMD, &con_dev, "%d: Cmd RDS\n", u);
	chan_set_sel(chan, 1);
	uptr->u5 |= URCSTA_READ;
	uptr->u3 = 0;
	return SCPE_OK;
    }
    chan_set_attn(chan);
    return SCPE_IOERR;
}

/* Handle transfer of data for printer */
t_stat
con_srv(UNIT *uptr) {
    int                 chan = UNIT_G_CHAN(uptr->flags);
    uint8		ch;
    int                 u = (uptr - con_unit);
    t_stat		r;

    /* Waiting for disconnect */
    if (uptr->u5 & URCSTA_WDISCO) {
	if (chan_stat(chan, DEV_DISCO)) {
	    sim_debug(DEBUG_DETAIL, &con_dev, " Disco\n");
	    chan_clear(chan, DEV_SEL|DEV_WEOR);
	    uptr->u5 &= ~ URCSTA_WDISCO;
	    sim_activate(uptr, 25);
	    return SCPE_OK;
	} else {
	    /* No disco yet, try again in a bit */
	    sim_activate(uptr, 50);
	    return SCPE_OK;
	}
    }

    uptr->u5 &= ~URCSTA_BUSY;	/* Clear busy */

    /* Copy next column over */
    if (uptr->u5 & URCSTA_WRITE) {
	switch(chan_read_char(chan, &ch, 0)) {
	case TIME_ERROR:
	case END_RECORD:
	     sim_putchar('\r');
	     sim_putchar('\n');
	sim_debug(DEBUG_EXP, &con_dev, "\n\r");
	     uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY;
	     uptr->u5 &= ~URCSTA_WRITE;
	     break;
       case DATA_OK:
	     ch &= 077;
	sim_debug(DEBUG_EXP, &con_dev, "%c", six_to_ascii[ch]);
	     sim_putchar(six_to_ascii[ch]);
	     break;
       }
       sim_activate(uptr, 100);
       return SCPE_OK;
    }

    /* Copy next column over */
    if ((uptr->u5 & URCSTA_INPUT) == 0 &&  uptr->u5 & URCSTA_READ) {
	sim_debug(DEBUG_DATA, &con_dev, "%d: Char > %02o\n", u, 
			con_data[u].ibuff[uptr->u3]);
        switch(chan_write_char(chan, &con_data[u].ibuff[uptr->u3],
            ((uptr->u3+1) == con_data[u].inptr)? DEV_REOR: 0)) {
        case TIME_ERROR:
        case END_RECORD:
	    uptr->u5 |= URCSTA_WDISCO|URCSTA_BUSY;
	    uptr->u5 &= ~URCSTA_READ;
	    chan_clear_attn_inq(chan);
	    con_data[u].inptr = 0;
	    break;
        case DATA_OK:
	    uptr->u3++;
            break;
        }
        sim_activate(uptr, 10);
	return SCPE_OK;
    }

    r = sim_poll_kbd();
    if (r & SCPE_KFLAG) {
	ch = r & 0377;
	if (uptr->u5 & URCSTA_INPUT) {
	   /* Handle end of buffer */
	   switch (ch) {
	   case '\r':
	   case '\n':
		uptr->u5 &= ~URCSTA_INPUT;
		sim_putchar('\r');
		sim_putchar('\n');
		chan_set_attn_inq(chan);
	        break;
	   case 033:
		uptr->u5 &= ~URCSTA_INPUT;
		con_data[u].inptr = 0;
		break;
	   case '\b':
		if (con_data[u].inptr != 0) {
		  con_data[u].inptr--;
		  sim_putchar(ch);
		}
		break;
	   default:
		if (con_data[u].inptr < sizeof(con_data[u].ibuff)) {
		    ch = ascii_to_six[0177&ch];
		    if (ch == 0xff) {
			sim_putchar('\007');
			break;
		    }
		    sim_putchar(six_to_ascii[ch]);
		    con_data[u].ibuff[con_data[u].inptr++] = ch;
		}
		break;
	   }
	 } else {
	    if (ch == 033) {
		if (con_data[u].inptr != 0) {
		    chan_clear_attn_inq(chan);
		} else {
#ifdef I7070
		    chan_set_attn_inq(chan);
#endif
	            sim_putchar('I');
	            sim_putchar(' ');
		    uptr->u5 |= URCSTA_INPUT;
		 }
		 con_data[u].inptr = 0;
	    }
        }
    }
    sim_activate(uptr, 500);
    return SCPE_OK;
}

#endif

